#include "basechart_plot.h"

#include "basechart_graph.h"
#include "basechart_layout.h"
#include "basechart_legend.h"
#include "basechart_plotitem.h"
#include "basechart_plottable.h"
#include "basechart_selection.h"

////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// BaseChartCustomPlot
////////////////////////////////////////////////////////////////////////////////////////////////////

/*! \class BaseChartCustomPlot

  \brief The central class of the library. This is the QWidget which displays
  the plot and interacts with the user.

  For tutorials on how to use BaseChartCustomPlot, see the website\n
  http://www.qcustomplot.com/
*/

/* start of documentation of inline functions */

/*! \fn BaseChartSelectionRect *BaseChartCustomPlot::selectionRect() const

  Allows access to the currently used BaseChartSelectionRect instance (or
  subclass thereof), that is used to handle and draw selection rect interactions
  (see \ref setSelectionRectMode).

  \see setSelectionRect
*/

/*! \fn BaseChartLayoutGrid *BaseChartCustomPlot::plotLayout() const

  Returns the top level layout of this BaseChartCustomPlot instance. It is a
  \ref BaseChartLayoutGrid, initially containing just one cell with the main
  BaseChartAxisRect inside.
*/

/* end of documentation of inline functions */
/* start of documentation of signals */

/*! \fn void BaseChartCustomPlot::mouseDoubleClick(QMouseEvent *event)

  This signal is emitted when the BaseChartCustomPlot receives a mouse double
  click event.
*/

/*! \fn void BaseChartCustomPlot::mousePress(QMouseEvent *event)

  This signal is emitted when the BaseChartCustomPlot receives a mouse press
  event.

  It is emitted before BaseChartCustomPlot handles any other mechanism like
  range dragging. So a slot connected to this signal can still influence the
  behaviour e.g. with \ref BaseChartAxisRect::setRangeDrag or \ref
  BaseChartAxisRect::setRangeDragAxes.
*/

/*! \fn void BaseChartCustomPlot::mouseMove(QMouseEvent *event)

  This signal is emitted when the BaseChartCustomPlot receives a mouse move
  event.

  It is emitted before BaseChartCustomPlot handles any other mechanism like
  range dragging. So a slot connected to this signal can still influence the
  behaviour e.g. with \ref BaseChartAxisRect::setRangeDrag or \ref
  BaseChartAxisRect::setRangeDragAxes.

  \warning It is discouraged to change the drag-axes with \ref
  BaseChartAxisRect::setRangeDragAxes here, because the dragging starting point
  was saved the moment the mouse was pressed. Thus it only has a meaning for the
  range drag axes that were set at that moment. If you want to change the drag
  axes, consider doing this in the \ref mousePress signal instead.
*/

/*! \fn void BaseChartCustomPlot::mouseRelease(QMouseEvent *event)

  This signal is emitted when the BaseChartCustomPlot receives a mouse release
  event.

  It is emitted before BaseChartCustomPlot handles any other mechanisms like
  object selection. So a slot connected to this signal can still influence the
  behaviour e.g. with \ref setInteractions or \ref
  BaseChartAbstractPlottable::setSelectable.
*/

/*! \fn void BaseChartCustomPlot::mouseWheel(QMouseEvent *event)

  This signal is emitted when the BaseChartCustomPlot receives a mouse wheel
  event.

  It is emitted before BaseChartCustomPlot handles any other mechanisms like
  range zooming. So a slot connected to this signal can still influence the
  behaviour e.g. with \ref BaseChartAxisRect::setRangeZoom, \ref
  BaseChartAxisRect::setRangeZoomAxes or \ref
  BaseChartAxisRect::setRangeZoomFactor.
*/

/*! \fn void BaseChartCustomPlot::plottableClick(BaseChartAbstractPlottable
  *plottable, int dataIndex, QMouseEvent *event)

  This signal is emitted when a plottable is clicked.

  \a event is the mouse event that caused the click and \a plottable is the
  plottable that received the click. The parameter \a dataIndex indicates the
  data point that was closest to the click position.

  \see plottableDoubleClick
*/

/*! \fn void
  BaseChartCustomPlot::plottableDoubleClick(BaseChartAbstractPlottable
  *plottable, int dataIndex, QMouseEvent *event)

  This signal is emitted when a plottable is double clicked.

  \a event is the mouse event that caused the click and \a plottable is the
  plottable that received the click. The parameter \a dataIndex indicates the
  data point that was closest to the click position.

  \see plottableClick
*/

/*! \fn void BaseChartCustomPlot::itemClick(BaseChartAbstractItem *item,
  QMouseEvent *event)

  This signal is emitted when an item is clicked.

  \a event is the mouse event that caused the click and \a item is the item that
  received the click.

  \see itemDoubleClick
*/

/*! \fn void BaseChartCustomPlot::itemDoubleClick(BaseChartAbstractItem *item,
  QMouseEvent *event)

  This signal is emitted when an item is double clicked.

  \a event is the mouse event that caused the click and \a item is the item that
  received the click.

  \see itemClick
*/

/*! \fn void BaseChartCustomPlot::axisClick(BaseChartAxis *axis,
  BaseChartAxis::SelectablePart part, QMouseEvent *event)

  This signal is emitted when an axis is clicked.

  \a event is the mouse event that caused the click, \a axis is the axis that
  received the click and \a part indicates the part of the axis that was
  clicked.

  \see axisDoubleClick
*/

/*! \fn void BaseChartCustomPlot::axisDoubleClick(BaseChartAxis *axis,
  BaseChartAxis::SelectablePart part, QMouseEvent *event)

  This signal is emitted when an axis is double clicked.

  \a event is the mouse event that caused the click, \a axis is the axis that
  received the click and \a part indicates the part of the axis that was
  clicked.

  \see axisClick
*/

/*! \fn void BaseChartCustomPlot::legendClick(BaseChartLegend *legend,
  BaseChartAbstractLegendItem *item, QMouseEvent *event)

  This signal is emitted when a legend (item) is clicked.

  \a event is the mouse event that caused the click, \a legend is the legend
  that received the click and \a item is the legend item that received the
  click. If only the legend and no item is clicked, \a item is \c nullptr. This
  happens for a click inside the legend padding or the space between two items.

  \see legendDoubleClick
*/

/*! \fn void BaseChartCustomPlot::legendDoubleClick(BaseChartLegend *legend,
  BaseChartAbstractLegendItem *item, QMouseEvent *event)

  This signal is emitted when a legend (item) is double clicked.

  \a event is the mouse event that caused the click, \a legend is the legend
  that received the click and \a item is the legend item that received the
  click. If only the legend and no item is clicked, \a item is \c nullptr. This
  happens for a click inside the legend padding or the space between two items.

  \see legendClick
*/

/*! \fn void BaseChartCustomPlot::selectionChangedByUser()

  This signal is emitted after the user has changed the selection in the
  BaseChartCustomPlot, e.g. by clicking. It is not emitted when the selection
  state of an object has changed programmatically by a direct call to
  <tt>setSelected()</tt>/<tt>setSelection()</tt> on an object or by calling \ref
  deselectAll.

  In addition to this signal, selectable objects also provide individual
  signals, for example \ref BaseChartAxis::selectionChanged or \ref
  BaseChartAbstractPlottable::selectionChanged. Note that those signals are
  emitted even if the selection state is changed programmatically.

  See the documentation of \ref setInteractions for details about the selection
  mechanism.

  \see selectedPlottables, selectedGraphs, selectedItems, selectedAxes,
  selectedLegends
*/

/*! \fn void BaseChartCustomPlot::beforeReplot()

  This signal is emitted immediately before a replot takes place (caused by a
  call to the slot \ref replot).

  It is safe to mutually connect the replot slot with this signal on two
  QCustomPlots to make them replot synchronously, it won't cause an infinite
  recursion.

  \see replot, afterReplot, afterLayout
*/

/*! \fn void BaseChartCustomPlot::afterLayout()

  This signal is emitted immediately after the layout step has been completed,
  which occurs right before drawing the plot. This is typically during a call to
  \ref replot, and in such cases this signal is emitted in between the signals
  \ref beforeReplot and \ref afterReplot. Unlike those signals however, this
  signal is also emitted during off-screen painting, such as when calling \ref
  toPixmap or \ref savePdf.

  The layout step queries all layouts and layout elements in the plot for their
  proposed size and arranges the objects accordingly as preparation for the
  subsequent drawing step. Through this signal, you have the opportunity to
  update certain things in your plot that depend crucially on the exact
  dimensions/positioning of layout elements such as axes and axis rects.

  \warning However, changing any parameters of this BaseChartCustomPlot instance
  which would normally affect the layouting (e.g. axis range order of
  magnitudes, tick label sizes, etc.) will not issue a second run of the layout
  step. It will propagate directly to the draw step and may cause graphical
  inconsistencies such as overlapping objects, if sizes or positions have
  changed.

  \see updateLayout, beforeReplot, afterReplot
*/

/*! \fn void BaseChartCustomPlot::afterReplot()

  This signal is emitted immediately after a replot has taken place (caused by a
  call to the slot \ref replot).

  It is safe to mutually connect the replot slot with this signal on two
  QCustomPlots to make them replot synchronously, it won't cause an infinite
  recursion.

  \see replot, beforeReplot, afterLayout
*/

/* end of documentation of signals */
/* start of documentation of public members */

/*! \var BaseChartAxis *BaseChartCustomPlot::xAxis

  A pointer to the primary x Axis (bottom) of the main axis rect of the plot.

  BaseChartCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref
  yAxis, \ref xAxis2, \ref yAxis2) and the \ref legend. They make it very easy
  working with plots that only have a single axis rect and at most one axis at
  each axis rect side. If you use \link thelayoutsystem the layout
  system\endlink to add multiple axis rects or multiple axes to one side, use
  the \ref BaseChartAxisRect::axis interface to access the new axes. If one of
  the four default axes or the default legend is removed due to manipulation of
  the layout system (e.g. by removing the main axis rect), the corresponding
  pointers become \c nullptr.

  If an axis convenience pointer is currently \c nullptr and a new axis rect or
  a corresponding axis is added in the place of the main axis rect,
  BaseChartCustomPlot resets the convenience pointers to the according new axes.
  Similarly the \ref legend convenience pointer will be reset if a legend is
  added after the main legend was removed before.
*/

/*! \var BaseChartAxis *BaseChartCustomPlot::yAxis

  A pointer to the primary y Axis (left) of the main axis rect of the plot.

  BaseChartCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref
  yAxis, \ref xAxis2, \ref yAxis2) and the \ref legend. They make it very easy
  working with plots that only have a single axis rect and at most one axis at
  each axis rect side. If you use \link thelayoutsystem the layout
  system\endlink to add multiple axis rects or multiple axes to one side, use
  the \ref BaseChartAxisRect::axis interface to access the new axes. If one of
  the four default axes or the default legend is removed due to manipulation of
  the layout system (e.g. by removing the main axis rect), the corresponding
  pointers become \c nullptr.

  If an axis convenience pointer is currently \c nullptr and a new axis rect or
  a corresponding axis is added in the place of the main axis rect,
  BaseChartCustomPlot resets the convenience pointers to the according new axes.
  Similarly the \ref legend convenience pointer will be reset if a legend is
  added after the main legend was removed before.
*/

/*! \var BaseChartAxis *BaseChartCustomPlot::xAxis2

  A pointer to the secondary x Axis (top) of the main axis rect of the plot.
  Secondary axes are invisible by default. Use BaseChartAxis::setVisible to
  change this (or use \ref BaseChartAxisRect::setupFullAxesBox).

  BaseChartCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref
  yAxis, \ref xAxis2, \ref yAxis2) and the \ref legend. They make it very easy
  working with plots that only have a single axis rect and at most one axis at
  each axis rect side. If you use \link thelayoutsystem the layout
  system\endlink to add multiple axis rects or multiple axes to one side, use
  the \ref BaseChartAxisRect::axis interface to access the new axes. If one of
  the four default axes or the default legend is removed due to manipulation of
  the layout system (e.g. by removing the main axis rect), the corresponding
  pointers become \c nullptr.

  If an axis convenience pointer is currently \c nullptr and a new axis rect or
  a corresponding axis is added in the place of the main axis rect,
  BaseChartCustomPlot resets the convenience pointers to the according new axes.
  Similarly the \ref legend convenience pointer will be reset if a legend is
  added after the main legend was removed before.
*/

/*! \var BaseChartAxis *BaseChartCustomPlot::yAxis2

  A pointer to the secondary y Axis (right) of the main axis rect of the plot.
  Secondary axes are invisible by default. Use BaseChartAxis::setVisible to
  change this (or use \ref BaseChartAxisRect::setupFullAxesBox).

  BaseChartCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref
  yAxis, \ref xAxis2, \ref yAxis2) and the \ref legend. They make it very easy
  working with plots that only have a single axis rect and at most one axis at
  each axis rect side. If you use \link thelayoutsystem the layout
  system\endlink to add multiple axis rects or multiple axes to one side, use
  the \ref BaseChartAxisRect::axis interface to access the new axes. If one of
  the four default axes or the default legend is removed due to manipulation of
  the layout system (e.g. by removing the main axis rect), the corresponding
  pointers become \c nullptr.

  If an axis convenience pointer is currently \c nullptr and a new axis rect or
  a corresponding axis is added in the place of the main axis rect,
  BaseChartCustomPlot resets the convenience pointers to the according new axes.
  Similarly the \ref legend convenience pointer will be reset if a legend is
  added after the main legend was removed before.
*/

/*! \var BaseChartLegend *BaseChartCustomPlot::legend

  A pointer to the default legend of the main axis rect. The legend is invisible
  by default. Use BaseChartLegend::setVisible to change this.

  BaseChartCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref
  yAxis, \ref xAxis2, \ref yAxis2) and the \ref legend. They make it very easy
  working with plots that only have a single axis rect and at most one axis at
  each axis rect side. If you use \link thelayoutsystem the layout
  system\endlink to add multiple legends to the plot, use the layout system
  interface to access the new legend. For example, legends can be placed inside
  an axis rect's \ref BaseChartAxisRect::insetLayout "inset layout", and must
  then also be accessed via the inset layout. If the default legend is removed
  due to manipulation of the layout system (e.g. by removing the main axis
  rect), the corresponding pointer becomes \c nullptr.

  If an axis convenience pointer is currently \c nullptr and a new axis rect or
  a corresponding axis is added in the place of the main axis rect,
  BaseChartCustomPlot resets the convenience pointers to the according new axes.
  Similarly the \ref legend convenience pointer will be reset if a legend is
  added after the main legend was removed before.
*/

/* end of documentation of public members */

/*!
  Constructs a BaseChartCustomPlot and sets reasonable default values.
*/

#define QCP_DEVICEPIXELRATIO_SUPPORTED
#define QCP_DEVICEPIXELRATIO_FLOAT

BaseChartCustomPlot::BaseChartCustomPlot(QWidget* parent)
    : QWidget(parent),
      xAxis(nullptr),
      yAxis(nullptr),
      xAxis2(nullptr),
      yAxis2(nullptr),
      legend(nullptr),
      mBufferDevicePixelRatio(1.0),  // will be adapted to primary screen below
      mPlotLayout(nullptr),
      mAutoAddPlottableToLegend(true),
      mAntialiasedElements(aeNone),
      mNotAntialiasedElements(aeNone),
      mInteractions(iNone),
      mSelectionTolerance(8),
      mNoAntialiasingOnDrag(false),
      mBackgroundBrush(Qt::white, Qt::SolidPattern),
      mBackgroundScaled(true),
      mBackgroundScaledMode(Qt::KeepAspectRatioByExpanding),
      mCurrentLayer(nullptr),
      mPlottingHints(phCacheLabels | phImmediateRefresh),
      mMultiSelectModifier(Qt::ControlModifier),
      mSelectionRectMode(srmNone),
      mSelectionRect(nullptr),
      mOpenGl(false),
      mMouseHasMoved(false),
      mMouseEventLayerable(nullptr),
      mMouseSignalLayerable(nullptr),
      mReplotting(false),
      mReplotQueued(false),
      mReplotTime(0),
      mReplotTimeAverage(0),
      mOpenGlMultisamples(16),
      mOpenGlAntialiasedElementsBackup(aeNone),
      mOpenGlCacheLabelsBackup(true) {
  setAttribute(Qt::WA_NoMousePropagation);
  setAttribute(Qt::WA_OpaquePaintEvent);
  setFocusPolicy(Qt::ClickFocus);
  setMouseTracking(true);
  QLocale currentLocale = locale();
  currentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
  setLocale(currentLocale);
#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
#ifdef QCP_DEVICEPIXELRATIO_FLOAT
  setBufferDevicePixelRatio(QWidget::devicePixelRatioF());
#else
  setBufferDevicePixelRatio(QWidget::devicePixelRatio());
#endif
#endif

  mOpenGlAntialiasedElementsBackup = mAntialiasedElements;
  mOpenGlCacheLabelsBackup = mPlottingHints.testFlag(phCacheLabels);
  // create initial layers:
  mLayers.append(new BaseChartLayer(this, QLatin1String("background")));
  mLayers.append(new BaseChartLayer(this, QLatin1String("grid")));
  mLayers.append(new BaseChartLayer(this, QLatin1String("main")));
  mLayers.append(new BaseChartLayer(this, QLatin1String("axes")));
  mLayers.append(new BaseChartLayer(this, QLatin1String("legend")));
  mLayers.append(new BaseChartLayer(this, QLatin1String("overlay")));
  updateLayerIndices();
  setCurrentLayer(QLatin1String("main"));
  layer(QLatin1String("overlay"))->setMode(BaseChartLayer::lmBuffered);

  // create initial layout, axis rect and legend:
  mPlotLayout = new BaseChartLayoutGrid;
  mPlotLayout->initializeParentPlot(this);
  mPlotLayout->setParent(this);  // important because if parent is QWidget,
                                 // QCPLayout::sizeConstraintsChanged will call
                                 // QWidget::updateGeometry
  mPlotLayout->setLayer(QLatin1String("main"));
  BaseChartAxisRect* defaultAxisRect = new BaseChartAxisRect(this, true);
  mPlotLayout->addElement(0, 0, defaultAxisRect);
  xAxis = defaultAxisRect->axis(BaseChartAxis::atBottom);
  yAxis = defaultAxisRect->axis(BaseChartAxis::atLeft);
  xAxis2 = defaultAxisRect->axis(BaseChartAxis::atTop);
  yAxis2 = defaultAxisRect->axis(BaseChartAxis::atRight);
  legend = new BaseChartLegend;
  legend->setVisible(false);
  defaultAxisRect->insetLayout()->addElement(legend,
                                             Qt::AlignRight | Qt::AlignTop);
  defaultAxisRect->insetLayout()->setMargins(QMargins(12, 12, 12, 12));

  defaultAxisRect->setLayer(QLatin1String("background"));
  xAxis->setLayer(QLatin1String("axes"));
  yAxis->setLayer(QLatin1String("axes"));
  xAxis2->setLayer(QLatin1String("axes"));
  yAxis2->setLayer(QLatin1String("axes"));
  xAxis->grid()->setLayer(QLatin1String("grid"));
  yAxis->grid()->setLayer(QLatin1String("grid"));
  xAxis2->grid()->setLayer(QLatin1String("grid"));
  yAxis2->grid()->setLayer(QLatin1String("grid"));
  legend->setLayer(QLatin1String("legend"));

  // create selection rect instance:
  mSelectionRect = new BaseChartSelectionRect(this);
  mSelectionRect->setLayer(QLatin1String("overlay"));

  setViewport(rect());  // needs to be called after mPlotLayout has been created

  replot(rpQueuedReplot);
}

BaseChartCustomPlot::~BaseChartCustomPlot() {
  clearPlottables();
  clearItems();

  if (mPlotLayout) {
    delete mPlotLayout;
    mPlotLayout = nullptr;
  }

  mCurrentLayer = nullptr;
  qDeleteAll(mLayers);  // don't use removeLayer, because it would prevent the
                        // last layer to be removed
  mLayers.clear();
}

/*!
  Sets which elements are forcibly drawn antialiased as an \a or combination of
  AntialiasedElement.

  This overrides the antialiasing settings for whole element groups, normally
  controlled with the \a setAntialiasing function on the individual elements. If
  an element is neither specified in \ref setAntialiasedElements nor in \ref
  setNotAntialiasedElements, the antialiasing setting on each individual element
  instance is used.

  For example, if \a antialiasedElements contains \ref aePlottables, all
  plottables will be drawn antialiased, no matter what the specific
  BaseChartAbstractPlottable::setAntialiased value was set to.

  if an element in \a antialiasedElements is already set in \ref
  setNotAntialiasedElements, it is removed from there.

  \see setNotAntialiasedElements
*/
void BaseChartCustomPlot::setAntialiasedElements(
    const AntialiasedElements& antialiasedElements) {
  mAntialiasedElements = antialiasedElements;

  // make sure elements aren't in mNotAntialiasedElements and
  // mAntialiasedElements simultaneously:
  if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
    mNotAntialiasedElements |= ~mAntialiasedElements;
}

/*!
  Sets whether the specified \a antialiasedElement is forcibly drawn
  antialiased.

  See \ref setAntialiasedElements for details.

  \see setNotAntialiasedElement
*/
void BaseChartCustomPlot::setAntialiasedElement(
    AntialiasedElement antialiasedElement, bool enabled) {
  if (!enabled && mAntialiasedElements.testFlag(antialiasedElement))
    mAntialiasedElements &= ~antialiasedElement;
  else if (enabled && !mAntialiasedElements.testFlag(antialiasedElement))
    mAntialiasedElements |= antialiasedElement;

  // make sure elements aren't in mNotAntialiasedElements and
  // mAntialiasedElements simultaneously:
  if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
    mNotAntialiasedElements |= ~mAntialiasedElements;
}

/*!
  Sets which elements are forcibly drawn not antialiased as an \a or combination
  of AntialiasedElement.

  This overrides the antialiasing settings for whole element groups, normally
  controlled with the \a setAntialiasing function on the individual elements. If
  an element is neither specified in \ref setAntialiasedElements nor in \ref
  setNotAntialiasedElements, the antialiasing setting on each individual element
  instance is used.

  For example, if \a notAntialiasedElements contains \ref aePlottables, no
  plottables will be drawn antialiased, no matter what the specific
  BaseChartAbstractPlottable::setAntialiased value was set to.

  if an element in \a notAntialiasedElements is already set in \ref
  setAntialiasedElements, it is removed from there.

  \see setAntialiasedElements
*/
void BaseChartCustomPlot::setNotAntialiasedElements(
    const AntialiasedElements& notAntialiasedElements) {
  mNotAntialiasedElements = notAntialiasedElements;

  // make sure elements aren't in mNotAntialiasedElements and
  // mAntialiasedElements simultaneously:
  if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
    mAntialiasedElements |= ~mNotAntialiasedElements;
}

/*!
  Sets whether the specified \a notAntialiasedElement is forcibly drawn not
  antialiased.

  See \ref setNotAntialiasedElements for details.

  \see setAntialiasedElement
*/
void BaseChartCustomPlot::setNotAntialiasedElement(
    AntialiasedElement notAntialiasedElement, bool enabled) {
  if (!enabled && mNotAntialiasedElements.testFlag(notAntialiasedElement))
    mNotAntialiasedElements &= ~notAntialiasedElement;
  else if (enabled && !mNotAntialiasedElements.testFlag(notAntialiasedElement))
    mNotAntialiasedElements |= notAntialiasedElement;

  // make sure elements aren't in mNotAntialiasedElements and
  // mAntialiasedElements simultaneously:
  if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
    mAntialiasedElements |= ~mNotAntialiasedElements;
}

/*!
  If set to true, adding a plottable (e.g. a graph) to the BaseChartCustomPlot
  automatically also adds the plottable to the legend
  (BaseChartCustomPlot::legend).

  \see addGraph, BaseChartLegend::addItem
*/
void BaseChartCustomPlot::setAutoAddPlottableToLegend(bool on) {
  mAutoAddPlottableToLegend = on;
}

/*!
  Sets the possible interactions of this BaseChartCustomPlot as an
  or-combination of \ref Interaction enums. There are the following types of
  interactions:

  <b>Axis range manipulation</b> is controlled via \ref iRangeDrag and \ref
  iRangeZoom. When the respective interaction is enabled, the user may drag axes
  ranges and zoom with the mouse wheel. For details how to control which axes
  the user may drag/zoom and in what orientations, see \ref
  BaseChartAxisRect::setRangeDrag, \ref BaseChartAxisRect::setRangeZoom, \ref
  BaseChartAxisRect::setRangeDragAxes, \ref BaseChartAxisRect::setRangeZoomAxes.

  <b>Plottable data selection</b> is controlled by \ref iSelectPlottables. If
  \ref iSelectPlottables is set, the user may select plottables (graphs, curves,
  bars,...) and their data by clicking on them or in their vicinity (\ref
  setSelectionTolerance). Whether the user can actually select a plottable and
  its data can further be restricted with the \ref
  BaseChartAbstractPlottable::setSelectable method on the specific plottable.
  For details, see the special page about the \ref dataselection "data selection
  mechanism". To retrieve a list of all currently selected plottables, call \ref
  selectedPlottables. If you're only interested in QCPGraphs, you may use the
  convenience function \ref selectedGraphs.

  <b>Item selection</b> is controlled by \ref iSelectItems. If \ref iSelectItems
  is set, the user may select items (QCPItemLine, QCPItemText,...) by clicking
  on them or in their vicinity. To find out whether a specific item is selected,
  call BaseChartAbstractItem::selected(). To retrieve a list of all currently
  selected items, call \ref selectedItems.

  <b>Axis selection</b> is controlled with \ref iSelectAxes. If \ref iSelectAxes
  is set, the user may select parts of the axes by clicking on them. What parts
  exactly (e.g. Axis base line, tick labels, axis label) are selectable can be
  controlled via \ref BaseChartAxis::setSelectableParts for each axis. To
  retrieve a list of all axes that currently contain selected parts, call \ref
  selectedAxes. Which parts of an axis are selected, can be retrieved with
  BaseChartAxis::selectedParts().

  <b>Legend selection</b> is controlled with \ref iSelectLegend. If this is set,
  the user may select the legend itself or individual items by clicking on them.
  What parts exactly are selectable can be controlled via \ref
  BaseChartLegend::setSelectableParts. To find out whether the legend or any of
  its child items are selected, check the value of
  BaseChartLegend::selectedParts. To find out which child items are selected,
  call \ref BaseChartLegend::selectedItems.

  <b>All other selectable elements</b> The selection of all other selectable
  objects (e.g. QCPTextElement, or your own layerable subclasses) is controlled
  with \ref iSelectOther. If set, the user may select those objects by clicking
  on them. To find out which are currently selected, you need to check their
  selected state explicitly.

  If the selection state has changed by user interaction, the \ref
  selectionChangedByUser signal is emitted. Each selectable object additionally
  emits an individual selectionChanged signal whenever their selection state has
  changed, i.e. not only by user interaction.

  To allow multiple objects to be selected by holding the selection modifier
  (\ref setMultiSelectModifier), set the flag \ref iMultiSelect.

  \note In addition to the selection mechanism presented here,
  BaseChartCustomPlot always emits corresponding signals, when an object is
  clicked or double clicked. see \ref plottableClick and \ref
  plottableDoubleClick for example.

  \see setInteraction, setSelectionTolerance
*/
void BaseChartCustomPlot::setInteractions(const Interactions& interactions) {
  mInteractions = interactions;
}

/*!
  Sets the single \a interaction of this BaseChartCustomPlot to \a enabled.

  For details about the interaction system, see \ref setInteractions.

  \see setInteractions
*/
void BaseChartCustomPlot::setInteraction(const Interaction& interaction,
                                         bool enabled) {
  if (!enabled && mInteractions.testFlag(interaction))
    mInteractions &= ~interaction;
  else if (enabled && !mInteractions.testFlag(interaction))
    mInteractions |= interaction;
}

/*!
  Sets the tolerance that is used to decide whether a click selects an object
  (e.g. a plottable) or not.

  If the user clicks in the vicinity of the line of e.g. a BaseChartGraph, it's
  only regarded as a potential selection when the minimum distance between the
  click position and the graph line is smaller than \a pixels. Objects that are
  defined by an area (e.g. QCPBars) only react to clicks directly inside the
  area and ignore this selection tolerance. In other words, it only has meaning
  for parts of objects that are too thin to exactly hit with a click and thus
  need such a tolerance.

  \see setInteractions, BaseChartLayerable::selectTest
*/
void BaseChartCustomPlot::setSelectionTolerance(int pixels) {
  mSelectionTolerance = pixels;
}

/*!
  Sets whether antialiasing is disabled for this BaseChartCustomPlot while the
  user is dragging axes ranges. If many objects, especially plottables, are
  drawn antialiased, this greatly improves performance during dragging. Thus it
  creates a more responsive user experience. As soon as the user stops dragging,
  the last replot is done with normal antialiasing, to restore high image
  quality.

  \see setAntialiasedElements, setNotAntialiasedElements
*/
void BaseChartCustomPlot::setNoAntialiasingOnDrag(bool enabled) {
  mNoAntialiasingOnDrag = enabled;
}

/*!
  Sets the plotting hints for this BaseChartCustomPlot instance as an \a or
  combination of PlottingHint.

  \see setPlottingHint
*/
void BaseChartCustomPlot::setPlottingHints(const PlottingHints& hints) {
  mPlottingHints = hints;
}

/*!
  Sets the specified plotting \a hint to \a enabled.

  \see setPlottingHints
*/
void BaseChartCustomPlot::setPlottingHint(PlottingHint hint, bool enabled) {
  PlottingHints newHints = mPlottingHints;
  if (!enabled)
    newHints &= ~hint;
  else
    newHints |= hint;

  if (newHints != mPlottingHints) setPlottingHints(newHints);
}

/*!
  Sets the keyboard modifier that will be recognized as multi-select-modifier.

  If \ref iMultiSelect is specified in \ref setInteractions, the user may select
  multiple objects (or data points) by clicking on them one after the other
  while holding down \a modifier.

  By default the multi-select-modifier is set to Qt::ControlModifier.

  \see setInteractions
*/
void BaseChartCustomPlot::setMultiSelectModifier(
    Qt::KeyboardModifier modifier) {
  mMultiSelectModifier = modifier;
}

/*!
  Sets how BaseChartCustomPlot processes mouse click-and-drag interactions by
  the user.

  If \a mode is \ref srmNone, the mouse drag is forwarded to the underlying
  objects. For example, BaseChartAxisRect may process a mouse drag by dragging
  axis ranges, see \ref BaseChartAxisRect::setRangeDrag. If \a mode is not \ref
  srmNone, the current selection rect (\ref selectionRect) becomes activated and
  allows e.g. rect zooming and data point selection.

  If you wish to provide your user both with axis range dragging and data
  selection/range zooming, use this method to switch between the modes just
  before the interaction is processed, e.g. in reaction to the \ref mousePress
  or \ref mouseMove signals. For example you could check whether the user is
  holding a certain keyboard modifier, and then decide which \a mode shall be
  set.

  If a selection rect interaction is currently active, and \a mode is set to
  \ref srmNone, the interaction is canceled (\ref
  BaseChartSelectionRect::cancel). Switching between any of the other modes will
  keep the selection rect active. Upon completion of the interaction, the
  behaviour is as defined by the currently set \a mode, not the mode that was
  set when the interaction started.

  \see setInteractions, setSelectionRect, BaseChartSelectionRect
*/
void BaseChartCustomPlot::setSelectionRectMode(SelectionRectMode mode) {
  if (mSelectionRect) {
    if (mode == srmNone)
      mSelectionRect->cancel();  // when switching to none, we immediately want
                                 // to abort a potentially active selection rect

    // disconnect old connections:
    if (mSelectionRectMode == srmSelect)
      disconnect(mSelectionRect, SIGNAL(accepted(QRect, QMouseEvent*)), this,
                 SLOT(processRectSelection(QRect, QMouseEvent*)));
    else if (mSelectionRectMode == srmZoom)
      disconnect(mSelectionRect, SIGNAL(accepted(QRect, QMouseEvent*)), this,
                 SLOT(processRectZoom(QRect, QMouseEvent*)));

    // establish new ones:
    if (mode == srmSelect)
      connect(mSelectionRect, SIGNAL(accepted(QRect, QMouseEvent*)), this,
              SLOT(processRectSelection(QRect, QMouseEvent*)));
    else if (mode == srmZoom)
      connect(mSelectionRect, SIGNAL(accepted(QRect, QMouseEvent*)), this,
              SLOT(processRectZoom(QRect, QMouseEvent*)));
  }

  mSelectionRectMode = mode;
}

/*!
  Sets the \ref BaseChartSelectionRect instance that BaseChartCustomPlot will
  use if \a mode is not \ref srmNone and the user performs a click-and-drag
  interaction. BaseChartCustomPlot takes ownership of the passed \a
  selectionRect. It can be accessed later via \ref selectionRect.

  This method is useful if you wish to replace the default
  BaseChartSelectionRect instance with an instance of a BaseChartSelectionRect
  subclass, to introduce custom behaviour of the selection rect.

  \see setSelectionRectMode
*/
void BaseChartCustomPlot::setSelectionRect(
    BaseChartSelectionRect* selectionRect) {
  delete mSelectionRect;

  mSelectionRect = selectionRect;

  if (mSelectionRect) {
    // establish connections with new selection rect:
    if (mSelectionRectMode == srmSelect)
      connect(mSelectionRect, SIGNAL(accepted(QRect, QMouseEvent*)), this,
              SLOT(processRectSelection(QRect, QMouseEvent*)));
    else if (mSelectionRectMode == srmZoom)
      connect(mSelectionRect, SIGNAL(accepted(QRect, QMouseEvent*)), this,
              SLOT(processRectZoom(QRect, QMouseEvent*)));
  }
}

/*!
  \warning This is still an experimental feature and its performance depends on
  the system that it runs on. Having multiple BaseChartCustomPlot widgets in one
  application with enabled OpenGL rendering might cause context conflicts on
  some systems.

  This method allows to enable OpenGL plot rendering, for increased plotting
  performance of graphically demanding plots (thick lines, translucent fills,
  etc.).

  If \a enabled is set to true, BaseChartCustomPlot will try to initialize
  OpenGL and, if successful, continue plotting with hardware acceleration. The
  parameter \a multisampling controls how many samples will be used per pixel,
  it essentially controls the antialiasing quality. If \a multisampling is set
  too high for the current graphics hardware, the maximum allowed value will be
  used.

  You can test whether switching to OpenGL rendering was successful by checking
  whether the according getter \a BaseChartCustomPlot::openGl() returns true. If
  the OpenGL initialization fails, rendering continues with the regular software
  rasterizer, and an according qDebug output is generated.

  If switching to OpenGL was successful, this method disables label caching
  (\ref setPlottingHint "setPlottingHint(phCacheLabels, false)") and turns on
  BaseChartCustomPlot's antialiasing override for all elements (\ref
  setAntialiasedElements "setAntialiasedElements(aeAll)"), leading to a higher
  quality output. The antialiasing override allows for pixel-grid aligned
  drawing in the OpenGL paint device. As stated before, in OpenGL rendering the
  actual antialiasing of the plot is controlled with \a multisampling. If \a
  enabled is set to false, the antialiasing/label caching settings are restored
  to what they were before OpenGL was enabled, if they weren't altered in the
  meantime.

  \note OpenGL support is only enabled if BaseChartCustomPlot is compiled with
  the macro \c QCUSTOMPLOT_USE_OPENGL defined. This define must be set before
  including the BaseChartCustomPlot header both during compilation of the
  BaseChartCustomPlot library as well as when compiling your application. It is
  best to just include the line <tt>DEFINES += QCUSTOMPLOT_USE_OPENGL</tt> in
  the respective qmake project files. \note If you are using a Qt version
  before 5.0, you must also add the module "opengl" to your \c QT variable in
  the qmake project files. For Qt versions 5.0 and higher, BaseChartCustomPlot
  switches to a newer OpenGL interface which is already in the "gui" module.
*/
void BaseChartCustomPlot::setOpenGl(bool enabled, int multisampling) {
  mOpenGlMultisamples = qMax(0, multisampling);
#ifdef QCUSTOMPLOT_USE_OPENGL
  mOpenGl = enabled;
  if (mOpenGl) {
    if (setupOpenGl()) {
      // backup antialiasing override and labelcaching setting so we can restore
      // upon disabling OpenGL
      mOpenGlAntialiasedElementsBackup = mAntialiasedElements;
      mOpenGlCacheLabelsBackup = mPlottingHints.testFlag(phCacheLabels);
      // set antialiasing override to antialias all (aligns gl pixel grid
      // properly), and disable label caching (would use software rasterizer for
      // pixmap caches):
      setAntialiasedElements(aeAll);
      setPlottingHint(phCacheLabels, false);
    } else {
      qDebug() << Q_FUNC_INFO
               << "Failed to enable OpenGL, continuing plotting without "
                  "hardware acceleration.";
      mOpenGl = false;
    }
  } else {
    // restore antialiasing override and labelcaching to what it was before
    // enabling OpenGL, if nobody changed it in the meantime:
    if (mAntialiasedElements == aeAll)
      setAntialiasedElements(mOpenGlAntialiasedElementsBackup);
    if (!mPlottingHints.testFlag(phCacheLabels))
      setPlottingHint(phCacheLabels, mOpenGlCacheLabelsBackup);
    freeOpenGl();
  }
  // recreate all paint buffers:
  mPaintBuffers.clear();
  setupPaintBuffers();
#else
  Q_UNUSED(enabled)
  qDebug() << Q_FUNC_INFO
           << "BaseChartCustomPlot can't use OpenGL because "
              "QCUSTOMPLOT_USE_OPENGL was not defined during compilation (add "
              "'DEFINES += QCUSTOMPLOT_USE_OPENGL' to your qmake .pro file)";
#endif
}

/*!
  Sets the viewport of this BaseChartCustomPlot. Usually users of
  BaseChartCustomPlot don't need to change the viewport manually.

  The viewport is the area in which the plot is drawn. All mechanisms, e.g.
  margin calculation take the viewport to be the outer border of the plot. The
  viewport normally is the rect() of the BaseChartCustomPlot widget, i.e. a rect
  with top left (0, 0) and size of the BaseChartCustomPlot widget.

  Don't confuse the viewport with the axis rect (BaseChartCustomPlot::axisRect).
  An axis rect is typically an area enclosed by four axes, where the
  graphs/plottables are drawn in. The viewport is larger and contains also the
  axes themselves, their tick numbers, their labels, or even additional axis
  rects, color scales and other layout elements.

  This function is used to allow arbitrary size exports with \ref toPixmap, \ref
  savePng, \ref savePdf, etc. by temporarily changing the viewport size.
*/
void BaseChartCustomPlot::setViewport(const QRect& rect) {
  mViewport = rect;
  if (mPlotLayout) mPlotLayout->setOuterRect(mViewport);
}

/*!
  Sets the device pixel ratio used by the paint buffers of this
  BaseChartCustomPlot instance.

  Normally, this doesn't need to be set manually, because it is initialized with
  the regular \a QWidget::devicePixelRatio which is configured by Qt to fit the
  display device (e.g. 1 for normal displays, 2 for High-DPI displays).

  Device pixel ratios are supported by Qt only for Qt versions since 5.4. If
  this method is called when BaseChartCustomPlot is being used with older Qt
  versions, outputs an according qDebug message and leaves the internal buffer
  device pixel ratio at 1.0.
*/
void BaseChartCustomPlot::setBufferDevicePixelRatio(double ratio) {
  if (!qFuzzyCompare(ratio, mBufferDevicePixelRatio)) {
#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
    mBufferDevicePixelRatio = ratio;
    foreach (QSharedPointer<BaseChartAbstractPaintBuffer> buffer, mPaintBuffers)
      buffer->setDevicePixelRatio(mBufferDevicePixelRatio);
      // Note: axis label cache has devicePixelRatio as part of cache hash, so
      // no need to manually clear cache here
#else
    qDebug() << Q_FUNC_INFO
             << "Device pixel ratios not supported for Qt versions before 5.4";
    mBufferDevicePixelRatio = 1.0;
#endif
  }
}

/*!
  Sets \a pm as the viewport background pixmap (see \ref setViewport). The
  pixmap is always drawn below all other objects in the plot.

  For cases where the provided pixmap doesn't have the same size as the
  viewport, scaling can be enabled with \ref setBackgroundScaled and the scaling
  mode (whether and how the aspect ratio is preserved) can be set with \ref
  setBackgroundScaledMode. To set all these options in one call, consider using
  the overloaded version of this function.

  If a background brush was set with \ref setBackground(const QBrush &brush),
  the viewport will first be filled with that brush, before drawing the
  background pixmap. This can be useful for background pixmaps with translucent
  areas.

  \see setBackgroundScaled, setBackgroundScaledMode
*/
void BaseChartCustomPlot::setBackground(const QPixmap& pm) {
  mBackgroundPixmap = pm;
  mScaledBackgroundPixmap = QPixmap();
}

/*!
  Sets the background brush of the viewport (see \ref setViewport).

  Before drawing everything else, the background is filled with \a brush. If a
  background pixmap was set with \ref setBackground(const QPixmap &pm), this
  brush will be used to fill the viewport before the background pixmap is drawn.
  This can be useful for background pixmaps with translucent areas.

  Set \a brush to Qt::NoBrush or Qt::Transparent to leave background
  transparent. This can be useful for exporting to image formats which support
  transparency, e.g. \ref savePng.

  \see setBackgroundScaled, setBackgroundScaledMode
*/
void BaseChartCustomPlot::setBackground(const QBrush& brush) {
  mBackgroundBrush = brush;
}

/*! \overload

  Allows setting the background pixmap of the viewport, whether it shall be
  scaled and how it shall be scaled in one call.

  \see setBackground(const QPixmap &pm), setBackgroundScaled,
  setBackgroundScaledMode
*/
void BaseChartCustomPlot::setBackground(const QPixmap& pm, bool scaled,
                                        Qt::AspectRatioMode mode) {
  mBackgroundPixmap = pm;
  mScaledBackgroundPixmap = QPixmap();
  mBackgroundScaled = scaled;
  mBackgroundScaledMode = mode;
}

/*!
  Sets whether the viewport background pixmap shall be scaled to fit the
  viewport. If \a scaled is set to true, control whether and how the aspect
  ratio of the original pixmap is preserved with \ref setBackgroundScaledMode.

  Note that the scaled version of the original pixmap is buffered, so there is
  no performance penalty on replots. (Except when the viewport dimensions are
  changed continuously.)

  \see setBackground, setBackgroundScaledMode
*/
void BaseChartCustomPlot::setBackgroundScaled(bool scaled) {
  mBackgroundScaled = scaled;
}

/*!
  If scaling of the viewport background pixmap is enabled (\ref
  setBackgroundScaled), use this function to define whether and how the aspect
  ratio of the original pixmap is preserved.

  \see setBackground, setBackgroundScaled
*/
void BaseChartCustomPlot::setBackgroundScaledMode(Qt::AspectRatioMode mode) {
  mBackgroundScaledMode = mode;
}

/*!
  Returns the plottable with \a index. If the index is invalid, returns \c
  nullptr.

  There is an overloaded version of this function with no parameter which
  returns the last added plottable, see BaseChartCustomPlot::plottable()

  \see plottableCount
*/
BaseChartAbstractPlottable* BaseChartCustomPlot::plottable(int index) {
  if (index >= 0 && index < mPlottables.size()) {
    return mPlottables.at(index);
  } else {
    qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
    return nullptr;
  }
}

/*! \overload

  Returns the last plottable that was added to the plot. If there are no
  plottables in the plot, returns \c nullptr.

  \see plottableCount
*/
BaseChartAbstractPlottable* BaseChartCustomPlot::plottable() {
  if (!mPlottables.isEmpty()) {
    return mPlottables.last();
  } else
    return nullptr;
}

/*!
  Removes the specified plottable from the plot and deletes it. If necessary,
  the corresponding legend item is also removed from the default legend
  (BaseChartCustomPlot::legend).

  Returns true on success.

  \see clearPlottables
*/
bool BaseChartCustomPlot::removePlottable(
    BaseChartAbstractPlottable* plottable) {
  if (!mPlottables.contains(plottable)) {
    qDebug() << Q_FUNC_INFO << "plottable not in list:"
             << reinterpret_cast<quintptr>(plottable);
    return false;
  }

  // remove plottable from legend:
  plottable->removeFromLegend();
  // special handling for QCPGraphs to maintain the simple graph interface:
  if (BaseChartGraph* graph = qobject_cast<BaseChartGraph*>(plottable))
    mGraphs.removeOne(graph);
  // remove plottable:
  delete plottable;
  mPlottables.removeOne(plottable);
  return true;
}

/*! \overload

  Removes and deletes the plottable by its \a index.
*/
bool BaseChartCustomPlot::removePlottable(int index) {
  if (index >= 0 && index < mPlottables.size())
    return removePlottable(mPlottables[index]);
  else {
    qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
    return false;
  }
}

/*!
  Removes all plottables from the plot and deletes them. Corresponding legend
  items are also removed from the default legend (BaseChartCustomPlot::legend).

  Returns the number of plottables removed.

  \see removePlottable
*/
int BaseChartCustomPlot::clearPlottables() {
  int c = mPlottables.size();
  for (int i = c - 1; i >= 0; --i) removePlottable(mPlottables[i]);
  return c;
}

/*!
  Returns the number of currently existing plottables in the plot

  \see plottable
*/
int BaseChartCustomPlot::plottableCount() const { return mPlottables.size(); }

/*!
  Returns a list of the selected plottables. If no plottables are currently
  selected, the list is empty.

  There is a convenience function if you're only interested in selected graphs,
  see \ref selectedGraphs.

  \see setInteractions, BaseChartAbstractPlottable::setSelectable,
  BaseChartAbstractPlottable::setSelection
*/
QList<BaseChartAbstractPlottable*> BaseChartCustomPlot::selectedPlottables()
    const {
  QList<BaseChartAbstractPlottable*> result;
  foreach (BaseChartAbstractPlottable* plottable, mPlottables) {
    if (plottable->selected()) result.append(plottable);
  }
  return result;
}

/*!
  Returns any plottable at the pixel position \a pos. Since it can capture all
  plottables, the return type is the abstract base class of all plottables,
  BaseChartAbstractPlottable.

  For details, and if you wish to specify a certain plottable type (e.g.
  BaseChartGraph), see the template method plottableAt<PlottableType>()

  \see plottableAt<PlottableType>(), itemAt, layoutElementAt
*/
BaseChartAbstractPlottable* BaseChartCustomPlot::plottableAt(
    const QPointF& pos, bool onlySelectable, int* dataIndex) const {
  return plottableAt<BaseChartAbstractPlottable>(pos, onlySelectable,
                                                 dataIndex);
}

/*!
  Returns whether this BaseChartCustomPlot instance contains the \a plottable.
*/
bool BaseChartCustomPlot::hasPlottable(
    BaseChartAbstractPlottable* plottable) const {
  return mPlottables.contains(plottable);
}

/*!
  Returns the graph with \a index. If the index is invalid, returns \c nullptr.

  There is an overloaded version of this function with no parameter which
  returns the last created graph, see BaseChartCustomPlot::graph()

  \see graphCount, addGraph
*/
BaseChartGraph* BaseChartCustomPlot::graph(int index) const {
  if (index >= 0 && index < mGraphs.size()) {
    return mGraphs.at(index);
  } else {
    qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
    return nullptr;
  }
}

/*! \overload

  Returns the last graph, that was created with \ref addGraph. If there are no
  graphs in the plot, returns \c nullptr.

  \see graphCount, addGraph
*/
BaseChartGraph* BaseChartCustomPlot::graph() const {
  if (!mGraphs.isEmpty()) {
    return mGraphs.last();
  } else
    return nullptr;
}

/*!
  Creates a new graph inside the plot. If \a keyAxis and \a valueAxis are left
  unspecified (0), the bottom (xAxis) is used as key and the left (yAxis) is
  used as value axis. If specified, \a keyAxis and \a valueAxis must reside in
  this BaseChartCustomPlot.

  \a keyAxis will be used as key axis (typically "x") and \a valueAxis as value
  axis (typically "y") for the graph.

  Returns a pointer to the newly created graph, or \c nullptr if adding the
  graph failed.

  \see graph, graphCount, removeGraph, clearGraphs
*/
BaseChartGraph* BaseChartCustomPlot::addGraph(BaseChartAxis* keyAxis,
                                              BaseChartAxis* valueAxis) {
  if (!keyAxis) keyAxis = xAxis;
  if (!valueAxis) valueAxis = yAxis;
  if (!keyAxis || !valueAxis) {
    qDebug() << Q_FUNC_INFO
             << "can't use default BaseChartCustomPlot xAxis or yAxis, because "
                "at least one is invalid (has been deleted)";
    return nullptr;
  }
  if (keyAxis->parentPlot() != this || valueAxis->parentPlot() != this) {
    qDebug() << Q_FUNC_INFO
             << "passed keyAxis or valueAxis doesn't have this "
                "BaseChartCustomPlot as parent";
    return nullptr;
  }

  BaseChartGraph* newGraph = new BaseChartGraph(keyAxis, valueAxis);
  newGraph->setName(QLatin1String("Graph ") + QString::number(mGraphs.size()));
  return newGraph;
}

/*!
  Removes the specified \a graph from the plot and deletes it. If necessary, the
  corresponding legend item is also removed from the default legend
  (BaseChartCustomPlot::legend). If any other graphs in the plot have a channel
  fill set towards the removed graph, the channel fill property of those graphs
  is reset to \c nullptr (no channel fill).

  Returns true on success.

  \see clearGraphs
*/
bool BaseChartCustomPlot::removeGraph(BaseChartGraph* graph) {
  return removePlottable(graph);
}

/*! \overload

  Removes and deletes the graph by its \a index.
*/
bool BaseChartCustomPlot::removeGraph(int index) {
  if (index >= 0 && index < mGraphs.size())
    return removeGraph(mGraphs[index]);
  else
    return false;
}

/*!
  Removes all graphs from the plot and deletes them. Corresponding legend items
  are also removed from the default legend (BaseChartCustomPlot::legend).

  Returns the number of graphs removed.

  \see removeGraph
*/
int BaseChartCustomPlot::clearGraphs() {
  int c = mGraphs.size();
  for (int i = c - 1; i >= 0; --i) removeGraph(mGraphs[i]);
  return c;
}

/*!
  Returns the number of currently existing graphs in the plot

  \see graph, addGraph
*/
int BaseChartCustomPlot::graphCount() const { return mGraphs.size(); }

/*!
  Returns a list of the selected graphs. If no graphs are currently selected,
  the list is empty.

  If you are not only interested in selected graphs but other plottables like
  QCPCurve, QCPBars, etc., use \ref selectedPlottables.

  \see setInteractions, selectedPlottables,
  BaseChartAbstractPlottable::setSelectable,
  BaseChartAbstractPlottable::setSelection
*/
QList<BaseChartGraph*> BaseChartCustomPlot::selectedGraphs() const {
  QList<BaseChartGraph*> result;
  foreach (BaseChartGraph* graph, mGraphs) {
    if (graph->selected()) result.append(graph);
  }
  return result;
}

/*!
  Returns the item with \a index. If the index is invalid, returns \c nullptr.

  There is an overloaded version of this function with no parameter which
  returns the last added item, see BaseChartCustomPlot::item()

  \see itemCount
*/
BaseChartAbstractItem* BaseChartCustomPlot::item(int index) const {
  if (index >= 0 && index < mItems.size()) {
    return mItems.at(index);
  } else {
    qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
    return nullptr;
  }
}

/*! \overload

  Returns the last item that was added to this plot. If there are no items in
  the plot, returns \c nullptr.

  \see itemCount
*/
BaseChartAbstractItem* BaseChartCustomPlot::item() const {
  if (!mItems.isEmpty()) {
    return mItems.last();
  } else
    return nullptr;
}

/*!
  Removes the specified item from the plot and deletes it.

  Returns true on success.

  \see clearItems
*/
bool BaseChartCustomPlot::removeItem(BaseChartAbstractItem* item) {
  if (mItems.contains(item)) {
    delete item;
    mItems.removeOne(item);
    return true;
  } else {
    qDebug() << Q_FUNC_INFO
             << "item not in list:" << reinterpret_cast<quintptr>(item);
    return false;
  }
}

/*! \overload

  Removes and deletes the item by its \a index.
*/
bool BaseChartCustomPlot::removeItem(int index) {
  if (index >= 0 && index < mItems.size())
    return removeItem(mItems[index]);
  else {
    qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
    return false;
  }
}

/*!
  Removes all items from the plot and deletes them.

  Returns the number of items removed.

  \see removeItem
*/
int BaseChartCustomPlot::clearItems() {
  int c = mItems.size();
  for (int i = c - 1; i >= 0; --i) removeItem(mItems[i]);
  return c;
}

/*!
  Returns the number of currently existing items in the plot

  \see item
*/
int BaseChartCustomPlot::itemCount() const { return mItems.size(); }

/*!
  Returns a list of the selected items. If no items are currently selected, the
  list is empty.

  \see setInteractions, BaseChartAbstractItem::setSelectable,
  BaseChartAbstractItem::setSelected
*/
QList<BaseChartAbstractItem*> BaseChartCustomPlot::selectedItems() const {
  QList<BaseChartAbstractItem*> result;
  foreach (BaseChartAbstractItem* item, mItems) {
    if (item->selected()) result.append(item);
  }
  return result;
}

/*!
  Returns the item at the pixel position \a pos. Since it can capture all items,
  the return type is the abstract base class of all items,
  BaseChartAbstractItem.

  For details, and if you wish to specify a certain item type (e.g.
  QCPItemLine), see the template method itemAt<ItemType>()

  \see itemAt<ItemType>(), plottableAt, layoutElementAt
*/
BaseChartAbstractItem* BaseChartCustomPlot::itemAt(const QPointF& pos,
                                                   bool onlySelectable) const {
  return itemAt<BaseChartAbstractItem>(pos, onlySelectable);
}

/*!
  Returns whether this BaseChartCustomPlot contains the \a item.

  \see item
*/
bool BaseChartCustomPlot::hasItem(BaseChartAbstractItem* item) const {
  return mItems.contains(item);
}

/*!
  Returns the layer with the specified \a name. If there is no layer with the
  specified name, \c nullptr is returned.

  Layer names are case-sensitive.

  \see addLayer, moveLayer, removeLayer
*/
BaseChartLayer* BaseChartCustomPlot::layer(const QString& name) const {
  foreach (BaseChartLayer* layer, mLayers) {
    if (layer->name() == name) return layer;
  }
  return nullptr;
}

/*! \overload

  Returns the layer by \a index. If the index is invalid, \c nullptr is
  returned.

  \see addLayer, moveLayer, removeLayer
*/
BaseChartLayer* BaseChartCustomPlot::layer(int index) const {
  if (index >= 0 && index < mLayers.size()) {
    return mLayers.at(index);
  } else {
    qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
    return nullptr;
  }
}

/*!
  Returns the layer that is set as current layer (see \ref setCurrentLayer).
*/
BaseChartLayer* BaseChartCustomPlot::currentLayer() const {
  return mCurrentLayer;
}

/*!
  Sets the layer with the specified \a name to be the current layer. All
  layerables (\ref BaseChartLayerable), e.g. plottables and items, are created
  on the current layer.

  Returns true on success, i.e. if there is a layer with the specified \a name
  in the BaseChartCustomPlot.

  Layer names are case-sensitive.

  \see addLayer, moveLayer, removeLayer, BaseChartLayerable::setLayer
*/
bool BaseChartCustomPlot::setCurrentLayer(const QString& name) {
  if (BaseChartLayer* newCurrentLayer = layer(name)) {
    return setCurrentLayer(newCurrentLayer);
  } else {
    qDebug() << Q_FUNC_INFO << "layer with name doesn't exist:" << name;
    return false;
  }
}

/*! \overload

  Sets the provided \a layer to be the current layer.

  Returns true on success, i.e. when \a layer is a valid layer in the
  BaseChartCustomPlot.

  \see addLayer, moveLayer, removeLayer
*/
bool BaseChartCustomPlot::setCurrentLayer(BaseChartLayer* layer) {
  if (!mLayers.contains(layer)) {
    qDebug() << Q_FUNC_INFO << "layer not a layer of this BaseChartCustomPlot:"
             << reinterpret_cast<quintptr>(layer);
    return false;
  }

  mCurrentLayer = layer;
  return true;
}

/*!
  Returns the number of currently existing layers in the plot

  \see layer, addLayer
*/
int BaseChartCustomPlot::layerCount() const { return mLayers.size(); }

/*!
  Adds a new layer to this BaseChartCustomPlot instance. The new layer will have
  the name \a name, which must be unique. Depending on \a insertMode, it is
  positioned either below or above \a otherLayer.

  Returns true on success, i.e. if there is no other layer named \a name and \a
  otherLayer is a valid layer inside this BaseChartCustomPlot.

  If \a otherLayer is 0, the highest layer in the BaseChartCustomPlot will be
  used.

  For an explanation of what layers are in BaseChartCustomPlot, see the
  documentation of \ref BaseChartLayer.

  \see layer, moveLayer, removeLayer
*/
bool BaseChartCustomPlot::addLayer(
    const QString& name, BaseChartLayer* otherLayer,
    BaseChartCustomPlot::LayerInsertMode insertMode) {
  if (!otherLayer) otherLayer = mLayers.last();
  if (!mLayers.contains(otherLayer)) {
    qDebug() << Q_FUNC_INFO
             << "otherLayer not a layer of this BaseChartCustomPlot:"
             << reinterpret_cast<quintptr>(otherLayer);
    return false;
  }
  if (layer(name)) {
    qDebug() << Q_FUNC_INFO << "A layer exists already with the name" << name;
    return false;
  }

  BaseChartLayer* newLayer = new BaseChartLayer(this, name);
  mLayers.insert(otherLayer->index() + (insertMode == limAbove ? 1 : 0),
                 newLayer);
  updateLayerIndices();
  setupPaintBuffers();  // associates new layer with the appropriate paint
                        // buffer
  return true;
}

/*!
  Removes the specified \a layer and returns true on success.

  All layerables (e.g. plottables and items) on the removed layer will be moved
  to the layer below \a layer. If \a layer is the bottom layer, the layerables
  are moved to the layer above. In both cases, the total rendering order of all
  layerables in the BaseChartCustomPlot is preserved.

  If \a layer is the current layer (\ref setCurrentLayer), the layer below (or
  above, if bottom layer) becomes the new current layer.

  It is not possible to remove the last layer of the plot.

  \see layer, addLayer, moveLayer
*/
bool BaseChartCustomPlot::removeLayer(BaseChartLayer* layer) {
  if (!mLayers.contains(layer)) {
    qDebug() << Q_FUNC_INFO << "layer not a layer of this BaseChartCustomPlot:"
             << reinterpret_cast<quintptr>(layer);
    return false;
  }
  if (mLayers.size() < 2) {
    qDebug() << Q_FUNC_INFO << "can't remove last layer";
    return false;
  }

  // append all children of this layer to layer below (if this is lowest layer,
  // prepend to layer above)
  int removedIndex = layer->index();
  bool isFirstLayer = removedIndex == 0;
  BaseChartLayer* targetLayer = isFirstLayer ? mLayers.at(removedIndex + 1)
                                             : mLayers.at(removedIndex - 1);
  QList<BaseChartLayerable*> children = layer->children();
  if (isFirstLayer)  // prepend in reverse order (such that relative order stays
                     // the same)
    std::reverse(children.begin(), children.end());
  foreach (BaseChartLayerable* child, children)
    child->moveToLayer(
        targetLayer,
        isFirstLayer);  // prepend if isFirstLayer, otherwise append

  // if removed layer is current layer, change current layer to layer
  // below/above:
  if (layer == mCurrentLayer) setCurrentLayer(targetLayer);

  // invalidate the paint buffer that was responsible for this layer:
  if (QSharedPointer<BaseChartAbstractPaintBuffer> pb =
          layer->mPaintBuffer.toStrongRef())
    pb->setInvalidated();

  // remove layer:
  delete layer;
  mLayers.removeOne(layer);
  updateLayerIndices();
  return true;
}

/*!
  Moves the specified \a layer either above or below \a otherLayer. Whether it's
  placed above or below is controlled with \a insertMode.

  Returns true on success, i.e. when both \a layer and \a otherLayer are valid
  layers in the BaseChartCustomPlot.

  \see layer, addLayer, moveLayer
*/
bool BaseChartCustomPlot::moveLayer(
    BaseChartLayer* layer, BaseChartLayer* otherLayer,
    BaseChartCustomPlot::LayerInsertMode insertMode) {
  if (!mLayers.contains(layer)) {
    qDebug() << Q_FUNC_INFO << "layer not a layer of this BaseChartCustomPlot:"
             << reinterpret_cast<quintptr>(layer);
    return false;
  }
  if (!mLayers.contains(otherLayer)) {
    qDebug() << Q_FUNC_INFO
             << "otherLayer not a layer of this BaseChartCustomPlot:"
             << reinterpret_cast<quintptr>(otherLayer);
    return false;
  }

  if (layer->index() > otherLayer->index())
    mLayers.move(layer->index(),
                 otherLayer->index() + (insertMode == limAbove ? 1 : 0));
  else if (layer->index() < otherLayer->index())
    mLayers.move(layer->index(),
                 otherLayer->index() + (insertMode == limAbove ? 0 : -1));

  // invalidate the paint buffers that are responsible for the layers:
  if (QSharedPointer<BaseChartAbstractPaintBuffer> pb =
          layer->mPaintBuffer.toStrongRef())
    pb->setInvalidated();
  if (QSharedPointer<BaseChartAbstractPaintBuffer> pb =
          otherLayer->mPaintBuffer.toStrongRef())
    pb->setInvalidated();

  updateLayerIndices();
  return true;
}

/*!
  Returns the number of axis rects in the plot.

  All axis rects can be accessed via BaseChartCustomPlot::axisRect().

  Initially, only one axis rect exists in the plot.

  \see axisRect, axisRects
*/
int BaseChartCustomPlot::axisRectCount() const { return axisRects().size(); }

/*!
  Returns the axis rect with \a index.

  Initially, only one axis rect (with index 0) exists in the plot. If multiple
  axis rects were added, all of them may be accessed with this function in a
  linear fashion (even when they are nested in a layout hierarchy or inside
  other axis rects via BaseChartAxisRect::insetLayout).

  The order of the axis rects is given by the fill order of the \ref QCPLayout
  that is holding them. For example, if the axis rects are in the top level grid
  layout (accessible via \ref BaseChartCustomPlot::plotLayout), they are ordered
  from left to right, top to bottom, if the layout's default \ref
  BaseChartLayoutGrid::setFillOrder "setFillOrder" of \ref
  BaseChartLayoutGrid::foColumnsFirst "foColumnsFirst" wasn't changed.

  If you want to access axis rects by their row and column index, use the layout
  interface. For example, use \ref BaseChartLayoutGrid::element of the top level
  grid layout, and \c qobject_cast the returned layout element to \ref
  BaseChartAxisRect. (See also \ref thelayoutsystem.)

  \see axisRectCount, axisRects, BaseChartLayoutGrid::setFillOrder
*/
BaseChartAxisRect* BaseChartCustomPlot::axisRect(int index) const {
  const QList<BaseChartAxisRect*> rectList = axisRects();
  if (index >= 0 && index < rectList.size()) {
    return rectList.at(index);
  } else {
    qDebug() << Q_FUNC_INFO << "invalid axis rect index" << index;
    return nullptr;
  }
}

/*!
  Returns all axis rects in the plot.

  The order of the axis rects is given by the fill order of the \ref QCPLayout
  that is holding them. For example, if the axis rects are in the top level grid
  layout (accessible via \ref BaseChartCustomPlot::plotLayout), they are ordered
  from left to right, top to bottom, if the layout's default \ref
  BaseChartLayoutGrid::setFillOrder "setFillOrder" of \ref
  BaseChartLayoutGrid::foColumnsFirst "foColumnsFirst" wasn't changed.

  \see axisRectCount, axisRect, BaseChartLayoutGrid::setFillOrder
*/
QList<BaseChartAxisRect*> BaseChartCustomPlot::axisRects() const {
  QList<BaseChartAxisRect*> result;
  QStack<BaseChartLayoutElement*> elementStack;
  if (mPlotLayout) elementStack.push(mPlotLayout);

  while (!elementStack.isEmpty()) {
    foreach (BaseChartLayoutElement* element,
             elementStack.pop()->elements(false)) {
      if (element) {
        elementStack.push(element);
        if (BaseChartAxisRect* ar = qobject_cast<BaseChartAxisRect*>(element))
          result.append(ar);
      }
    }
  }

  return result;
}

/*!
  Returns the layout element at pixel position \a pos. If there is no element at
  that position, returns \c nullptr.

  Only visible elements are used. If \ref BaseChartLayoutElement::setVisible on
  the element itself or on any of its parent elements is set to false, it will
  not be considered.

  \see itemAt, plottableAt
*/
BaseChartLayoutElement* BaseChartCustomPlot::layoutElementAt(
    const QPointF& pos) const {
  BaseChartLayoutElement* currentElement = mPlotLayout;
  bool searchSubElements = true;
  while (searchSubElements && currentElement) {
    searchSubElements = false;
    foreach (BaseChartLayoutElement* subElement,
             currentElement->elements(false)) {
      if (subElement && subElement->realVisibility() &&
          subElement->selectTest(pos, false) >= 0) {
        currentElement = subElement;
        searchSubElements = true;
        break;
      }
    }
  }
  return currentElement;
}

/*!
  Returns the layout element of type \ref BaseChartAxisRect at pixel position \a
  pos. This method ignores other layout elements even if they are visually in
  front of the axis rect (e.g. a \ref BaseChartLegend). If there is no axis rect
  at that position, returns \c nullptr.

  Only visible axis rects are used. If \ref BaseChartLayoutElement::setVisible
  on the axis rect itself or on any of its parent elements is set to false, it
  will not be considered.

  \see layoutElementAt
*/
BaseChartAxisRect* BaseChartCustomPlot::axisRectAt(const QPointF& pos) const {
  BaseChartAxisRect* result = nullptr;
  BaseChartLayoutElement* currentElement = mPlotLayout;
  bool searchSubElements = true;
  while (searchSubElements && currentElement) {
    searchSubElements = false;
    foreach (BaseChartLayoutElement* subElement,
             currentElement->elements(false)) {
      if (subElement && subElement->realVisibility() &&
          subElement->selectTest(pos, false) >= 0) {
        currentElement = subElement;
        searchSubElements = true;
        if (BaseChartAxisRect* ar =
                qobject_cast<BaseChartAxisRect*>(currentElement))
          result = ar;
        break;
      }
    }
  }
  return result;
}

/*!
  Returns the axes that currently have selected parts, i.e. whose selection
  state is not \ref BaseChartAxis::spNone.

  \see selectedPlottables, selectedLegends, setInteractions,
  BaseChartAxis::setSelectedParts, BaseChartAxis::setSelectableParts
*/
QList<BaseChartAxis*> BaseChartCustomPlot::selectedAxes() const {
  QList<BaseChartAxis*> result, allAxes;
  foreach (BaseChartAxisRect* rect, axisRects()) allAxes << rect->axes();

  foreach (BaseChartAxis* axis, allAxes) {
    if (axis->selectedParts() != BaseChartAxis::spNone) result.append(axis);
  }

  return result;
}

/*!
  Returns the legends that currently have selected parts, i.e. whose selection
  state is not \ref BaseChartLegend::spNone.

  \see selectedPlottables, selectedAxes, setInteractions,
  BaseChartLegend::setSelectedParts, BaseChartLegend::setSelectableParts,
  BaseChartLegend::selectedItems
*/
QList<BaseChartLegend*> BaseChartCustomPlot::selectedLegends() const {
  QList<BaseChartLegend*> result;

  QStack<BaseChartLayoutElement*> elementStack;
  if (mPlotLayout) elementStack.push(mPlotLayout);

  while (!elementStack.isEmpty()) {
    foreach (BaseChartLayoutElement* subElement,
             elementStack.pop()->elements(false)) {
      if (subElement) {
        elementStack.push(subElement);
        if (BaseChartLegend* leg = qobject_cast<BaseChartLegend*>(subElement)) {
          if (leg->selectedParts() != BaseChartLegend::spNone)
            result.append(leg);
        }
      }
    }
  }

  return result;
}

/*!
  Deselects all layerables (plottables, items, axes, legends,...) of the
  BaseChartCustomPlot.

  Since calling this function is not a user interaction, this does not emit the
  \ref selectionChangedByUser signal. The individual selectionChanged signals
  are emitted though, if the objects were previously selected.

  \see setInteractions, selectedPlottables, selectedItems, selectedAxes,
  selectedLegends
*/
void BaseChartCustomPlot::deselectAll() {
  foreach (BaseChartLayer* layer, mLayers) {
    foreach (BaseChartLayerable* layerable, layer->children())
      layerable->deselectEvent(nullptr);
  }
}

/*!
  Causes a complete replot into the internal paint buffer(s). Finally, the
  widget surface is refreshed with the new buffer contents. This is the method
  that must be called to make changes to the plot, e.g. on the axis ranges or
  data points of graphs, visible.

  The parameter \a refreshPriority can be used to fine-tune the timing of the
  replot. For example if your application calls \ref replot very quickly in
  succession (e.g. multiple independent functions change some aspects of the
  plot and each wants to make sure the change gets replotted), it is advisable
  to set \a refreshPriority to \ref BaseChartCustomPlot::rpQueuedReplot. This
  way, the actual replotting is deferred to the next event loop iteration.
  Multiple successive calls of \ref replot with this priority will only cause a
  single replot, avoiding redundant replots and improving performance.

  Under a few circumstances, BaseChartCustomPlot causes a replot by itself.
  Those are resize events of the BaseChartCustomPlot widget and user
  interactions (object selection and range dragging/zooming).

  Before the replot happens, the signal \ref beforeReplot is emitted. After the
  replot, \ref afterReplot is emitted. It is safe to mutually connect the replot
  slot with any of those two signals on two QCustomPlots to make them replot
  synchronously, it won't cause an infinite recursion.

  If a layer is in mode \ref BaseChartLayer::lmBuffered (\ref
  BaseChartLayer::setMode), it is also possible to replot only that specific
  layer via \ref BaseChartLayer::replot. See the documentation there for
  details.

  \see replotTime
*/
void BaseChartCustomPlot::replot(
    BaseChartCustomPlot::RefreshPriority refreshPriority) {
  if (refreshPriority == BaseChartCustomPlot::rpQueuedReplot) {
    if (!mReplotQueued) {
      mReplotQueued = true;
      QTimer::singleShot(0, this, SLOT(replot()));
    }
    return;
  }

  if (mReplotting)  // incase signals loop back to replot slot
    return;
  mReplotting = true;
  mReplotQueued = false;
  emit beforeReplot();

  QElapsedTimer replotTimer;
  replotTimer.start();

  updateLayout();
  // draw all layered objects (grid, axes, plottables, items, legend,...) into
  // their buffers:
  setupPaintBuffers();
  foreach (BaseChartLayer* layer, mLayers) layer->drawToPaintBuffer();
  foreach (QSharedPointer<BaseChartAbstractPaintBuffer> buffer, mPaintBuffers)
    buffer->setInvalidated(false);

  if ((refreshPriority == rpRefreshHint &&
       mPlottingHints.testFlag(phImmediateRefresh)) ||
      refreshPriority == rpImmediateRefresh)
    repaint();
  else
    update();

  mReplotTime = replotTimer.nsecsElapsed() * 1e-6;

  if (!qFuzzyIsNull(mReplotTimeAverage))
    mReplotTimeAverage =
        mReplotTimeAverage * 0.9 +
        mReplotTime * 0.1;  // exponential moving average with a time constant
                            // of 10 last replots
  else
    mReplotTimeAverage = mReplotTime;  // no previous replots to average with,
                                       // so initialize with replot time

  emit afterReplot();
  mReplotting = false;
}

/*!
  Returns the time in milliseconds that the last replot took. If \a average is
  set to true, an exponential moving average over the last couple of replots is
  returned.

  \see replot
*/
double BaseChartCustomPlot::replotTime(bool average) const {
  return average ? mReplotTimeAverage : mReplotTime;
}

/*!
  Rescales the axes such that all plottables (like graphs) in the plot are fully
  visible.

  if \a onlyVisiblePlottables is set to true, only the plottables that have
  their visibility set to true (BaseChartLayerable::setVisible), will be used to
  rescale the axes.

  \see BaseChartAbstractPlottable::rescaleAxes, BaseChartAxis::rescale
*/
void BaseChartCustomPlot::rescaleAxes(bool onlyVisiblePlottables) {
  QList<BaseChartAxis*> allAxes;
  foreach (BaseChartAxisRect* rect, axisRects()) allAxes << rect->axes();

  foreach (BaseChartAxis* axis, allAxes) axis->rescale(onlyVisiblePlottables);
}

/*!
  Saves a PDF with the vectorized plot to the file \a fileName. The axis ratio
  as well as the scale of texts and lines will be derived from the specified \a
  width and \a height. This means, the output will look like the normal
  on-screen output of a BaseChartCustomPlot widget with the corresponding pixel
  width and height. If either \a width or \a height is zero, the exported image
  will have the same dimensions as the BaseChartCustomPlot widget currently has.

  Setting \a exportPen to \ref epNoCosmetic allows to disable the use of
  cosmetic pens when drawing to the PDF file. Cosmetic pens are pens with
  numerical width 0, which are always drawn as a one pixel wide line, no matter
  what zoom factor is set in the PDF-Viewer. For more information about cosmetic
  pens, see the QPainter and QPen documentation.

  The objects of the plot will appear in the current selection state. If you
  don't want any selected objects to be painted in their selected look, deselect
  everything with \ref deselectAll before calling this function.

  Returns true on success.

  \warning
  \li If you plan on editing the exported PDF file with a vector graphics editor
  like Inkscape, it is advised to set \a exportPen to \ref epNoCosmetic to avoid
  losing those cosmetic lines (which might be quite many, because cosmetic pens
  are the default for e.g. axes and tick marks). \li If calling this function
  inside the constructor of the parent of the BaseChartCustomPlot widget (i.e.
  the MainWindow constructor, if BaseChartCustomPlot is inside the MainWindow),
  always provide explicit non-zero widths and heights. If you leave \a width or
  \a height as 0 (default), this function uses the current width and height of
  the BaseChartCustomPlot widget. However, in Qt, these aren't defined yet
  inside the constructor, so you would get an image that has strange
  widths/heights.

  \a pdfCreator and \a pdfTitle may be used to set the according metadata fields
  in the resulting PDF file.

  \note On Android systems, this method does nothing and issues an according
  qDebug warning message. This is also the case if for other reasons the define
  flag \c QT_NO_PRINTER is set.

  \see savePng, saveBmp, saveJpg, saveRastered
*/
bool BaseChartCustomPlot::savePdf(const QString& fileName, int width,
                                  int height, ExportPen exportPen,
                                  const QString& pdfCreator,
                                  const QString& pdfTitle) {
  bool success = false;
#ifdef QT_NO_PRINTER
  Q_UNUSED(fileName)
  Q_UNUSED(exportPen)
  Q_UNUSED(width)
  Q_UNUSED(height)
  Q_UNUSED(pdfCreator)
  Q_UNUSED(pdfTitle)
  qDebug() << Q_FUNC_INFO
           << "Qt was built without printer support (QT_NO_PRINTER). PDF not "
              "created.";
#else
  int newWidth, newHeight;
  if (width == 0 || height == 0) {
    newWidth = this->width();
    newHeight = this->height();
  } else {
    newWidth = width;
    newHeight = height;
  }

  QPrinter printer(QPrinter::ScreenResolution);
  printer.setOutputFileName(fileName);
  printer.setOutputFormat(QPrinter::PdfFormat);
  printer.setColorMode(QPrinter::Color);
  printer.printEngine()->setProperty(QPrintEngine::PPK_Creator, pdfCreator);
  printer.printEngine()->setProperty(QPrintEngine::PPK_DocumentName, pdfTitle);
  QRect oldViewport = viewport();
  setViewport(QRect(0, 0, newWidth, newHeight));

  QPageLayout pageLayout;
  pageLayout.setMode(QPageLayout::FullPageMode);
  pageLayout.setOrientation(QPageLayout::Portrait);
  pageLayout.setMargins(QMarginsF(0, 0, 0, 0));
  pageLayout.setPageSize(QPageSize(viewport().size(), QPageSize::Point,
                                   QString(), QPageSize::ExactMatch));
  printer.setPageLayout(pageLayout);

  BaseChartPainter printpainter;
  if (printpainter.begin(&printer)) {
    printpainter.setMode(BaseChartPainter::pmVectorized);
    printpainter.setMode(BaseChartPainter::pmNoCaching);
    printpainter.setMode(BaseChartPainter::pmNonCosmetic,
                         exportPen == epNoCosmetic);
    printpainter.setWindow(mViewport);
    if (mBackgroundBrush.style() != Qt::NoBrush &&
        mBackgroundBrush.color() != Qt::white &&
        mBackgroundBrush.color() != Qt::transparent &&
        mBackgroundBrush.color().alpha() >
            0)  // draw pdf background color if not white/transparent
      printpainter.fillRect(viewport(), mBackgroundBrush);
    draw(&printpainter);
    printpainter.end();
    success = true;
  }
  setViewport(oldViewport);
#endif  // QT_NO_PRINTER
  return success;
}

/*!
  Saves a PNG image file to \a fileName on disc. The output plot will have the
  dimensions \a width and \a height in pixels, multiplied by \a scale. If either
  \a width or \a height is zero, the current width and height of the
  BaseChartCustomPlot widget is used instead. Line widths and texts etc. are not
  scaled up when larger widths/heights are used. If you want that effect, use
  the \a scale parameter.

  For example, if you set both \a width and \a height to 100 and \a scale to 2,
  you will end up with an image file of size 200*200 in which all graphical
  elements are scaled up by factor 2 (line widths, texts, etc.). This scaling is
  not done by stretching a 100*100 image, the result will have full 200*200
  pixel resolution.

  If you use a high scaling factor, it is recommended to enable antialiasing for
  all elements by temporarily setting \ref
  BaseChartCustomPlot::setAntialiasedElements to \ref aeAll as this allows
  BaseChartCustomPlot to place objects with sub-pixel accuracy.

  image compression can be controlled with the \a quality parameter which must
  be between 0 and 100 or -1 to use the default setting.

  The \a resolution will be written to the image file header and has no direct
  consequence for the quality or the pixel size. However, if opening the image
  with a tool which respects the metadata, it will be able to scale the image to
  match either a given size in real units of length (inch, centimeters, etc.),
  or the target display DPI. You can specify in which units \a resolution is
  given, by setting \a resolutionUnit. The \a resolution is converted to the
  format's expected resolution unit internally.

  Returns true on success. If this function fails, most likely the PNG format
  isn't supported by the system, see Qt docs about
  QImageWriter::supportedImageFormats().

  The objects of the plot will appear in the current selection state. If you
  don't want any selected objects to be painted in their selected look, deselect
  everything with \ref deselectAll before calling this function.

  If you want the PNG to have a transparent background, call \ref
  setBackground(const QBrush &brush) with no brush (Qt::NoBrush) or a
  transparent color (Qt::transparent), before saving.

  \warning If calling this function inside the constructor of the parent of the
  BaseChartCustomPlot widget (i.e. the MainWindow constructor, if
  BaseChartCustomPlot is inside the MainWindow), always provide explicit
  non-zero widths and heights. If you leave \a width or \a height as 0
  (default), this function uses the current width and height of the
  BaseChartCustomPlot widget. However, in Qt, these aren't defined yet inside
  the constructor, so you would get an image that has strange widths/heights.

  \see savePdf, saveBmp, saveJpg, saveRastered
*/
bool BaseChartCustomPlot::savePng(const QString& fileName, int width,
                                  int height, double scale, int quality,
                                  int resolution,
                                  ResolutionUnit resolutionUnit) {
  return saveRastered(fileName, width, height, scale, "PNG", quality,
                      resolution, resolutionUnit);
}

/*!
  Saves a JPEG image file to \a fileName on disc. The output plot will have the
  dimensions \a width and \a height in pixels, multiplied by \a scale. If either
  \a width or \a height is zero, the current width and height of the
  BaseChartCustomPlot widget is used instead. Line widths and texts etc. are not
  scaled up when larger widths/heights are used. If you want that effect, use
  the \a scale parameter.

  For example, if you set both \a width and \a height to 100 and \a scale to 2,
  you will end up with an image file of size 200*200 in which all graphical
  elements are scaled up by factor 2 (line widths, texts, etc.). This scaling is
  not done by stretching a 100*100 image, the result will have full 200*200
  pixel resolution.

  If you use a high scaling factor, it is recommended to enable antialiasing for
  all elements by temporarily setting \ref
  BaseChartCustomPlot::setAntialiasedElements to \ref aeAll as this allows
  BaseChartCustomPlot to place objects with sub-pixel accuracy.

  image compression can be controlled with the \a quality parameter which must
  be between 0 and 100 or -1 to use the default setting.

  The \a resolution will be written to the image file header and has no direct
  consequence for the quality or the pixel size. However, if opening the image
  with a tool which respects the metadata, it will be able to scale the image to
  match either a given size in real units of length (inch, centimeters, etc.),
  or the target display DPI. You can specify in which units \a resolution is
  given, by setting \a resolutionUnit. The \a resolution is converted to the
  format's expected resolution unit internally.

  Returns true on success. If this function fails, most likely the JPEG format
  isn't supported by the system, see Qt docs about
  QImageWriter::supportedImageFormats().

  The objects of the plot will appear in the current selection state. If you
  don't want any selected objects to be painted in their selected look, deselect
  everything with \ref deselectAll before calling this function.

  \warning If calling this function inside the constructor of the parent of the
  BaseChartCustomPlot widget (i.e. the MainWindow constructor, if
  BaseChartCustomPlot is inside the MainWindow), always provide explicit
  non-zero widths and heights. If you leave \a width or \a height as 0
  (default), this function uses the current width and height of the
  BaseChartCustomPlot widget. However, in Qt, these aren't defined yet inside
  the constructor, so you would get an image that has strange widths/heights.

  \see savePdf, savePng, saveBmp, saveRastered
*/
bool BaseChartCustomPlot::saveJpg(const QString& fileName, int width,
                                  int height, double scale, int quality,
                                  int resolution,
                                  ResolutionUnit resolutionUnit) {
  return saveRastered(fileName, width, height, scale, "JPG", quality,
                      resolution, resolutionUnit);
}

/*!
  Saves a BMP image file to \a fileName on disc. The output plot will have the
  dimensions \a width and \a height in pixels, multiplied by \a scale. If either
  \a width or \a height is zero, the current width and height of the
  BaseChartCustomPlot widget is used instead. Line widths and texts etc. are not
  scaled up when larger widths/heights are used. If you want that effect, use
  the \a scale parameter.

  For example, if you set both \a width and \a height to 100 and \a scale to 2,
  you will end up with an image file of size 200*200 in which all graphical
  elements are scaled up by factor 2 (line widths, texts, etc.). This scaling is
  not done by stretching a 100*100 image, the result will have full 200*200
  pixel resolution.

  If you use a high scaling factor, it is recommended to enable antialiasing for
  all elements by temporarily setting \ref
  BaseChartCustomPlot::setAntialiasedElements to \ref aeAll as this allows
  BaseChartCustomPlot to place objects with sub-pixel accuracy.

  The \a resolution will be written to the image file header and has no direct
  consequence for the quality or the pixel size. However, if opening the image
  with a tool which respects the metadata, it will be able to scale the image to
  match either a given size in real units of length (inch, centimeters, etc.),
  or the target display DPI. You can specify in which units \a resolution is
  given, by setting \a resolutionUnit. The \a resolution is converted to the
  format's expected resolution unit internally.

  Returns true on success. If this function fails, most likely the BMP format
  isn't supported by the system, see Qt docs about
  QImageWriter::supportedImageFormats().

  The objects of the plot will appear in the current selection state. If you
  don't want any selected objects to be painted in their selected look, deselect
  everything with \ref deselectAll before calling this function.

  \warning If calling this function inside the constructor of the parent of the
  BaseChartCustomPlot widget (i.e. the MainWindow constructor, if
  BaseChartCustomPlot is inside the MainWindow), always provide explicit
  non-zero widths and heights. If you leave \a width or \a height as 0
  (default), this function uses the current width and height of the
  BaseChartCustomPlot widget. However, in Qt, these aren't defined yet inside
  the constructor, so you would get an image that has strange widths/heights.

  \see savePdf, savePng, saveJpg, saveRastered
*/
bool BaseChartCustomPlot::saveBmp(const QString& fileName, int width,
                                  int height, double scale, int resolution,
                                  ResolutionUnit resolutionUnit) {
  return saveRastered(fileName, width, height, scale, "BMP", -1, resolution,
                      resolutionUnit);
}

/*! \internal

  Returns a minimum size hint that corresponds to the minimum size of the top
  level layout
  (\ref plotLayout). To prevent BaseChartCustomPlot from being collapsed to
  size/width zero, set a minimum size (setMinimumSize) either on the whole
  BaseChartCustomPlot or on any layout elements inside the plot. This is
  especially important, when placed in a QLayout where other components try to
  take in as much space as possible (e.g. QMdiArea).
*/
QSize BaseChartCustomPlot::minimumSizeHint() const {
  return mPlotLayout->minimumOuterSizeHint();
}

/*! \internal

  Returns a size hint that is the same as \ref minimumSizeHint.

*/
QSize BaseChartCustomPlot::sizeHint() const {
  return mPlotLayout->minimumOuterSizeHint();
}

/*! \internal

  Event handler for when the BaseChartCustomPlot widget needs repainting. This
  does not cause a \ref replot, but draws the internal buffer on the widget
  surface.
*/
void BaseChartCustomPlot::paintEvent(QPaintEvent* event) {
  Q_UNUSED(event)
  BaseChartPainter painter(this);
  if (painter.isActive()) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    painter.setRenderHint(
        QPainter::Antialiasing);  // to make Antialiasing look good if using the
                                  // OpenGL graphicssystem
#endif
    if (mBackgroundBrush.style() != Qt::NoBrush)
      painter.fillRect(mViewport, mBackgroundBrush);
    drawBackground(&painter);
    foreach (QSharedPointer<BaseChartAbstractPaintBuffer> buffer, mPaintBuffers)
      buffer->draw(&painter);
  }
}

/*! \internal

  Event handler for a resize of the BaseChartCustomPlot widget. The viewport
  (which becomes the outer rect of mPlotLayout) is resized appropriately.
  Finally a \ref replot is performed.
*/
void BaseChartCustomPlot::resizeEvent(QResizeEvent* event) {
  Q_UNUSED(event)
  // resize and repaint the buffer:
  setViewport(rect());
  replot(rpQueuedRefresh);  // queued refresh is important here, to prevent
                            // painting issues in some contexts (e.g. MDI
                            // subwindow)
}

/*! \internal

 Event handler for when a double click occurs. Emits the \ref mouseDoubleClick
 signal, then determines the layerable under the cursor and forwards the event
 to it. Finally, emits the specialized signals when certain objecs are clicked
 (e.g. \ref plottableDoubleClick, \ref axisDoubleClick, etc.).

 \see mousePressEvent, mouseReleaseEvent
*/
void BaseChartCustomPlot::mouseDoubleClickEvent(QMouseEvent* event) {
  emit mouseDoubleClick(event);
  mMouseHasMoved = false;
  mMousePressPos = event->pos();

  // determine layerable under the cursor (this event is called instead of the
  // second press event in a double-click):
  QList<QVariant> details;
  QList<BaseChartLayerable*> candidates =
      layerableListAt(mMousePressPos, false, &details);
  for (int i = 0; i < candidates.size(); ++i) {
    event->accept();  // default impl of BaseChartLayerable's mouse events
                      // ignore the event, in that case propagate to next
                      // candidate in list
    candidates.at(i)->mouseDoubleClickEvent(event, details.at(i));
    if (event->isAccepted()) {
      mMouseEventLayerable = candidates.at(i);
      mMouseEventLayerableDetails = details.at(i);
      break;
    }
  }

  // emit specialized object double click signals:
  if (!candidates.isEmpty()) {
    if (BaseChartAbstractPlottable* ap =
            qobject_cast<BaseChartAbstractPlottable*>(candidates.first())) {
      int dataIndex = 0;
      if (!details.first().value<BaseChartDataSelection>().isEmpty())
        dataIndex =
            details.first().value<BaseChartDataSelection>().dataRange().begin();
      emit plottableDoubleClick(ap, dataIndex, event);
    } else if (BaseChartAxis* ax =
                   qobject_cast<BaseChartAxis*>(candidates.first()))
      emit axisDoubleClick(
          ax, details.first().value<BaseChartAxis::SelectablePart>(), event);
    else if (BaseChartAbstractItem* ai =
                 qobject_cast<BaseChartAbstractItem*>(candidates.first()))
      emit itemDoubleClick(ai, event);
    else if (BaseChartLegend* lg =
                 qobject_cast<BaseChartLegend*>(candidates.first()))
      emit legendDoubleClick(lg, nullptr, event);
    else if (BaseChartAbstractLegendItem* li =
                 qobject_cast<BaseChartAbstractLegendItem*>(candidates.first()))
      emit legendDoubleClick(li->parentLegend(), li, event);
  }

  event->accept();  // in case BaseChartLayerable reimplementation manipulates
                    // event accepted state. In QWidget event system,
                    // BaseChartCustomPlot wants to accept the event.
}

/*! \internal

  Event handler for when a mouse button is pressed. Emits the mousePress signal.

  If the current \ref setSelectionRectMode is not \ref srmNone, passes the event
  to the selection rect. Otherwise determines the layerable under the cursor and
  forwards the event to it.

  \see mouseMoveEvent, mouseReleaseEvent
*/
void BaseChartCustomPlot::mousePressEvent(QMouseEvent* event) {
  emit mousePress(event);
  // save some state to tell in releaseEvent whether it was a click:
  mMouseHasMoved = false;
  mMousePressPos = event->pos();

  if (mSelectionRect && mSelectionRectMode != srmNone) {
    if (mSelectionRectMode != srmZoom ||
        qobject_cast<BaseChartAxisRect*>(
            axisRectAt(mMousePressPos)))  // in zoom mode only activate
                                          // selection rect if on an axis rect
      mSelectionRect->startSelection(event);
  } else {
    // no selection rect interaction, prepare for click signal emission and
    // forward event to layerable under the cursor:
    QList<QVariant> details;
    QList<BaseChartLayerable*> candidates =
        layerableListAt(mMousePressPos, false, &details);
    if (!candidates.isEmpty()) {
      mMouseSignalLayerable =
          candidates
              .first();  // candidate for signal emission is always topmost hit
                         // layerable (signal emitted in release event)
      mMouseSignalLayerableDetails = details.first();
    }
    // forward event to topmost candidate which accepts the event:
    for (int i = 0; i < candidates.size(); ++i) {
      event->accept();  // default impl of BaseChartLayerable's mouse events
                        // call ignore() on the event, in that case propagate to
                        // next candidate in list
      candidates.at(i)->mousePressEvent(event, details.at(i));
      if (event->isAccepted()) {
        mMouseEventLayerable = candidates.at(i);
        mMouseEventLayerableDetails = details.at(i);
        break;
      }
    }
  }

  event->accept();  // in case BaseChartLayerable reimplementation manipulates
                    // event accepted state. In QWidget event system,
                    // BaseChartCustomPlot wants to accept the event.
}

/*! \internal

  Event handler for when the cursor is moved. Emits the \ref mouseMove signal.

  If the selection rect (\ref setSelectionRect) is currently active, the event
  is forwarded to it in order to update the rect geometry.

  Otherwise, if a layout element has mouse capture focus (a mousePressEvent
  happened on top of the layout element before), the mouseMoveEvent is forwarded
  to that element.

  \see mousePressEvent, mouseReleaseEvent
*/
void BaseChartCustomPlot::mouseMoveEvent(QMouseEvent* event) {
  emit mouseMove(event);

  if (!mMouseHasMoved && (mMousePressPos - event->pos()).manhattanLength() > 3)
    mMouseHasMoved = true;  // moved too far from mouse press position, don't
                            // handle as click on mouse release

  if (mSelectionRect && mSelectionRect->isActive())
    mSelectionRect->moveSelection(event);
  else if (mMouseEventLayerable)  // call event of affected layerable:
    mMouseEventLayerable->mouseMoveEvent(event, mMousePressPos);

  event->accept();  // in case BaseChartLayerable reimplementation manipulates
                    // event accepted state. In QWidget event system,
                    // BaseChartCustomPlot wants to accept the event.
}

/*! \internal

  Event handler for when a mouse button is released. Emits the \ref mouseRelease
  signal.

  If the mouse was moved less than a certain threshold in any direction since
  the \ref mousePressEvent, it is considered a click which causes the selection
  mechanism (if activated via \ref setInteractions) to possibly change selection
  states accordingly. Further, specialized mouse click signals are emitted (e.g.
  \ref plottableClick, \ref axisClick, etc.)

  If a layerable is the mouse capturer (a \ref mousePressEvent happened on top
  of the layerable before), the \ref mouseReleaseEvent is forwarded to that
  element.

  \see mousePressEvent, mouseMoveEvent
*/
void BaseChartCustomPlot::mouseReleaseEvent(QMouseEvent* event) {
  emit mouseRelease(event);

  if (!mMouseHasMoved)  // mouse hasn't moved (much) between press and release,
                        // so handle as click
  {
    if (mSelectionRect &&
        mSelectionRect
            ->isActive())  // a simple click shouldn't successfully finish a
                           // selection rect, so cancel it here
      mSelectionRect->cancel();
    if (event->button() == Qt::LeftButton) processPointSelection(event);

    // emit specialized click signals of BaseChartCustomPlot instance:
    if (BaseChartAbstractPlottable* ap =
            qobject_cast<BaseChartAbstractPlottable*>(mMouseSignalLayerable)) {
      int dataIndex = 0;
      if (!mMouseSignalLayerableDetails.value<BaseChartDataSelection>()
               .isEmpty())
        dataIndex = mMouseSignalLayerableDetails.value<BaseChartDataSelection>()
                        .dataRange()
                        .begin();
      emit plottableClick(ap, dataIndex, event);
    } else if (BaseChartAxis* ax =
                   qobject_cast<BaseChartAxis*>(mMouseSignalLayerable))
      emit axisClick(
          ax,
          mMouseSignalLayerableDetails.value<BaseChartAxis::SelectablePart>(),
          event);
    else if (BaseChartAbstractItem* ai =
                 qobject_cast<BaseChartAbstractItem*>(mMouseSignalLayerable))
      emit itemClick(ai, event);
    else if (BaseChartLegend* lg =
                 qobject_cast<BaseChartLegend*>(mMouseSignalLayerable))
      emit legendClick(lg, nullptr, event);
    else if (BaseChartAbstractLegendItem* li =
                 qobject_cast<BaseChartAbstractLegendItem*>(
                     mMouseSignalLayerable))
      emit legendClick(li->parentLegend(), li, event);
    mMouseSignalLayerable = nullptr;
  }

  if (mSelectionRect &&
      mSelectionRect->isActive())  // Note: if a click was detected above, the
                                   // selection rect is canceled there
  {
    // finish selection rect, the appropriate action will be taken via
    // signal-slot connection:
    mSelectionRect->endSelection(event);
  } else {
    // call event of affected layerable:
    if (mMouseEventLayerable) {
      mMouseEventLayerable->mouseReleaseEvent(event, mMousePressPos);
      mMouseEventLayerable = nullptr;
    }
  }

  if (noAntialiasingOnDrag()) replot(rpQueuedReplot);

  event->accept();  // in case BaseChartLayerable reimplementation manipulates
                    // event accepted state. In QWidget event system,
                    // BaseChartCustomPlot wants to accept the event.
}

/*! \internal

  Event handler for mouse wheel events. First, the \ref mouseWheel signal is
  emitted. Then determines the affected layerable and forwards the event to it.
*/
void BaseChartCustomPlot::wheelEvent(QWheelEvent* event) {
  emit mouseWheel(event);

#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
  const QPointF pos = event->pos();
#else
  const QPointF pos = event->position();
#endif

  // forward event to layerable under cursor:
  foreach (BaseChartLayerable* candidate, layerableListAt(pos, false)) {
    event->accept();  // default impl of BaseChartLayerable's mouse events
                      // ignore the event, in that case propagate to next
                      // candidate in list
    candidate->wheelEvent(event);
    if (event->isAccepted()) break;
  }
  event->accept();  // in case BaseChartLayerable reimplementation manipulates
                    // event accepted state. In QWidget event system,
                    // BaseChartCustomPlot wants to accept the event.
}

/*! \internal

  This function draws the entire plot, including background pixmap, with the
  specified \a painter. It does not make use of the paint buffers like \ref
  replot, so this is the function typically used by saving/exporting methods
  such as \ref savePdf or \ref toPainter.

  Note that it does not fill the background with the background brush (as the
  user may specify with \ref setBackground(const QBrush &brush)), this is up to
  the respective functions calling this method.
*/
void BaseChartCustomPlot::draw(BaseChartPainter* painter) {
  updateLayout();

  // draw viewport background pixmap:
  drawBackground(painter);

  // draw all layered objects (grid, axes, plottables, items, legend,...):
  foreach (BaseChartLayer* layer, mLayers) layer->draw(painter);

  /* Debug code to draw all layout element rects
  foreach (BaseChartLayoutElement *el, findChildren<BaseChartLayoutElement*>())
  {
    painter->setBrush(Qt::NoBrush);
    painter->setPen(QPen(QColor(0, 0, 0, 100), 0, Qt::DashLine));
    painter->drawRect(el->rect());
    painter->setPen(QPen(QColor(255, 0, 0, 100), 0, Qt::DashLine));
    painter->drawRect(el->outerRect());
  }
  */
}

/*! \internal

  Performs the layout update steps defined by \ref
  BaseChartLayoutElement::UpdatePhase, by calling \ref
  BaseChartLayoutElement::update on the main plot layout.

  Here, the layout elements calculate their positions and margins, and prepare
  for the following draw call.
*/
void BaseChartCustomPlot::updateLayout() {
  // run through layout phases:
  mPlotLayout->update(BaseChartLayoutElement::upPreparation);
  mPlotLayout->update(BaseChartLayoutElement::upMargins);
  mPlotLayout->update(BaseChartLayoutElement::upLayout);

  emit afterLayout();
}

/*! \internal

  Draws the viewport background pixmap of the plot.

  If a pixmap was provided via \ref setBackground, this function buffers the
  scaled version depending on \ref setBackgroundScaled and \ref
  setBackgroundScaledMode and then draws it inside the viewport with the
  provided \a painter. The scaled version is buffered in mScaledBackgroundPixmap
  to prevent expensive rescaling at every redraw. It is only updated, when the
  axis rect has changed in a way that requires a rescale of the background
  pixmap (this is dependent on the \ref setBackgroundScaledMode), or when a
  differend axis background pixmap was set.

  Note that this function does not draw a fill with the background brush
  (\ref setBackground(const QBrush &brush)) beneath the pixmap.

  \see setBackground, setBackgroundScaled, setBackgroundScaledMode
*/
void BaseChartCustomPlot::drawBackground(BaseChartPainter* painter) {
  // Note: background color is handled in individual replot/save functions

  // draw background pixmap (on top of fill, if brush specified):
  if (!mBackgroundPixmap.isNull()) {
    if (mBackgroundScaled) {
      // check whether mScaledBackground needs to be updated:
      QSize scaledSize(mBackgroundPixmap.size());
      scaledSize.scale(mViewport.size(), mBackgroundScaledMode);
      if (mScaledBackgroundPixmap.size() != scaledSize)
        mScaledBackgroundPixmap = mBackgroundPixmap.scaled(
            mViewport.size(), mBackgroundScaledMode, Qt::SmoothTransformation);
      painter->drawPixmap(mViewport.topLeft(), mScaledBackgroundPixmap,
                          QRect(0, 0, mViewport.width(), mViewport.height()) &
                              mScaledBackgroundPixmap.rect());
    } else {
      painter->drawPixmap(mViewport.topLeft(), mBackgroundPixmap,
                          QRect(0, 0, mViewport.width(), mViewport.height()));
    }
  }
}

/*! \internal

  Goes through the layers and makes sure this BaseChartCustomPlot instance holds
  the correct number of paint buffers and that they have the correct
  configuration (size, pixel ratio, etc.). Allocations, reallocations and
  deletions of paint buffers are performed as necessary. It also associates the
  paint buffers with the layers, so they draw themselves into the right buffer
  when \ref BaseChartLayer::drawToPaintBuffer is called. This means it
  associates adjacent \ref BaseChartLayer::lmLogical layers to a mutual paint
  buffer and creates dedicated paint buffers for layers in \ref
  BaseChartLayer::lmBuffered mode.

  This method uses \ref createPaintBuffer to create new paint buffers.

  After this method, the paint buffers are empty (filled with \c
  Qt::transparent) and invalidated (so an attempt to replot only a single
  buffered layer causes a full replot).

  This method is called in every \ref replot call, prior to actually drawing the
  layers (into their associated paint buffer). If the paint buffers don't need
  changing/reallocating, this method basically leaves them alone and thus
  finishes very fast.
*/
void BaseChartCustomPlot::setupPaintBuffers() {
  int bufferIndex = 0;
  if (mPaintBuffers.isEmpty())
    mPaintBuffers.append(
        QSharedPointer<BaseChartAbstractPaintBuffer>(createPaintBuffer()));

  for (int layerIndex = 0; layerIndex < mLayers.size(); ++layerIndex) {
    BaseChartLayer* layer = mLayers.at(layerIndex);
    if (layer->mode() == BaseChartLayer::lmLogical) {
      layer->mPaintBuffer = mPaintBuffers.at(bufferIndex).toWeakRef();
    } else if (layer->mode() == BaseChartLayer::lmBuffered) {
      ++bufferIndex;
      if (bufferIndex >= mPaintBuffers.size())
        mPaintBuffers.append(
            QSharedPointer<BaseChartAbstractPaintBuffer>(createPaintBuffer()));
      layer->mPaintBuffer = mPaintBuffers.at(bufferIndex).toWeakRef();
      if (layerIndex < mLayers.size() - 1 &&
          mLayers.at(layerIndex + 1)->mode() ==
              BaseChartLayer::lmLogical)  // not last layer, and next one is
                                          // logical, so prepare another buffer
                                          // for next layerables
      {
        ++bufferIndex;
        if (bufferIndex >= mPaintBuffers.size())
          mPaintBuffers.append(QSharedPointer<BaseChartAbstractPaintBuffer>(
              createPaintBuffer()));
      }
    }
  }
  // remove unneeded buffers:
  while (mPaintBuffers.size() - 1 > bufferIndex) mPaintBuffers.removeLast();
  // resize buffers to viewport size and clear contents:
  foreach (QSharedPointer<BaseChartAbstractPaintBuffer> buffer, mPaintBuffers) {
    buffer->setSize(
        viewport().size());  // won't do anything if already correct size
    buffer->clear(Qt::transparent);
    buffer->setInvalidated();
  }
}

/*! \internal

  This method is used by \ref setupPaintBuffers when it needs to create new
  paint buffers.

  Depending on the current setting of \ref setOpenGl, and the current Qt
  version, different backends (subclasses of \ref BaseChartAbstractPaintBuffer)
  are created, initialized with the proper size and device pixel ratio, and
  returned.
*/
BaseChartAbstractPaintBuffer* BaseChartCustomPlot::createPaintBuffer() {
  if (mOpenGl) {
    qDebug()
        << Q_FUNC_INFO
        << "OpenGL enabled even though no support for it compiled in, this "
           "shouldn't have happened. Falling back to pixmap paint buffer.";
    return new BaseChartPaintBufferPixmap(viewport().size(),
                                          mBufferDevicePixelRatio);
  } else
    return new BaseChartPaintBufferPixmap(viewport().size(),
                                          mBufferDevicePixelRatio);
}

/*!
  This method returns whether any of the paint buffers held by this
  BaseChartCustomPlot instance are invalidated.

  If any buffer is invalidated, a partial replot (\ref BaseChartLayer::replot)
  is not allowed and always causes a full replot (\ref
  BaseChartCustomPlot::replot) of all layers. This is the case when for example
  the layer order has changed, new layers were added or removed, layer modes
  were changed (\ref BaseChartLayer::setMode), or layerables were added or
  removed.

  \see BaseChartAbstractPaintBuffer::setInvalidated
*/
bool BaseChartCustomPlot::hasInvalidatedPaintBuffers() {
  foreach (QSharedPointer<BaseChartAbstractPaintBuffer> buffer, mPaintBuffers) {
    if (buffer->invalidated()) return true;
  }
  return false;
}

/*! \internal

  When \ref setOpenGl is set to true, this method is used to initialize OpenGL
  (create a context, surface, paint device).

  Returns true on success.

  If this method is successful, all paint buffers should be deleted and then
  reallocated by calling \ref setupPaintBuffers, so the OpenGL-based paint
  buffer subclasses (\ref QCPPaintBufferGlPbuffer, \ref QCPPaintBufferGlFbo) are
  used for subsequent replots.

  \see freeOpenGl
*/
bool BaseChartCustomPlot::setupOpenGl() { return false; }

/*! \internal

  When \ref setOpenGl is set to false, this method is used to deinitialize
  OpenGL (releases the context and frees resources).

  After OpenGL is disabled, all paint buffers should be deleted and then
  reallocated by calling \ref setupPaintBuffers, so the standard software
  rendering paint buffer subclass (\ref BaseChartPaintBufferPixmap) is used for
  subsequent replots.

  \see setupOpenGl
*/
void BaseChartCustomPlot::freeOpenGl() {}

/*! \internal

  This method is used by \ref BaseChartAxisRect::removeAxis to report removed
  axes to the BaseChartCustomPlot so it may clear its
  BaseChartCustomPlot::xAxis, yAxis, xAxis2 and yAxis2 members accordingly.
*/
void BaseChartCustomPlot::axisRemoved(BaseChartAxis* axis) {
  if (xAxis == axis) xAxis = nullptr;
  if (xAxis2 == axis) xAxis2 = nullptr;
  if (yAxis == axis) yAxis = nullptr;
  if (yAxis2 == axis) yAxis2 = nullptr;

  // Note: No need to take care of range drag axes and range zoom axes, because
  // they are stored in smart pointers
}

/*! \internal

  This method is used by the BaseChartLegend destructor to report legend removal
  to the BaseChartCustomPlot so it may clear its BaseChartCustomPlot::legend
  member accordingly.
*/
void BaseChartCustomPlot::legendRemoved(BaseChartLegend* legend) {
  if (this->legend == legend) this->legend = nullptr;
}

/*! \internal

  This slot is connected to the selection rect's \ref
  BaseChartSelectionRect::accepted signal when \ref setSelectionRectMode is set
  to \ref srmSelect.

  First, it determines which axis rect was the origin of the selection rect
  judging by the starting point of the selection. Then it goes through the
  plottables (\ref QCPAbstractPlottable1D to be precise) associated with that
  axis rect and finds the data points that are in \a rect. It does this by
  querying their \ref QCPAbstractPlottable1D::selectTestRect method.

  Then, the actual selection is done by calling the plottables' \ref
  BaseChartAbstractPlottable::selectEvent, placing the found selected data
  points in the \a details parameter as <tt>QVariant(\ref
  BaseChartDataSelection)</tt>. All plottables that weren't touched by \a rect
  receive a \ref BaseChartAbstractPlottable::deselectEvent.

  \see processRectZoom
*/
void BaseChartCustomPlot::processRectSelection(QRect rect, QMouseEvent* event) {
  typedef QPair<BaseChartAbstractPlottable*, BaseChartDataSelection>
      SelectionCandidate;
  typedef QMultiMap<int, SelectionCandidate>
      SelectionCandidates;  // map key is number of selected data points, so we
                            // have selections sorted by size

  bool selectionStateChanged = false;

  if (mInteractions.testFlag(iSelectPlottables)) {
    SelectionCandidates potentialSelections;
    QRectF rectF(rect.normalized());
    if (BaseChartAxisRect* affectedAxisRect = axisRectAt(rectF.topLeft())) {
      // determine plottables that were hit by the rect and thus are candidates
      // for selection:
      foreach (BaseChartAbstractPlottable* plottable,
               affectedAxisRect->plottables()) {
        if (BaseChartPlottableInterface1D* plottableInterface =
                plottable->interface1D()) {
          BaseChartDataSelection dataSel =
              plottableInterface->selectTestRect(rectF, true);
          if (!dataSel.isEmpty())
            potentialSelections.insert(dataSel.dataPointCount(),
                                       SelectionCandidate(plottable, dataSel));
        }
      }

      if (!mInteractions.testFlag(iMultiSelect)) {
        // only leave plottable with most selected points in map, since we will
        // only select a single plottable:
        if (!potentialSelections.isEmpty()) {
          SelectionCandidates::iterator it = potentialSelections.begin();
          while (it != std::prev(potentialSelections
                                     .end()))  // erase all except last element
            it = potentialSelections.erase(it);
        }
      }

      bool additive = event->modifiers().testFlag(mMultiSelectModifier);
      // deselect all other layerables if not additive selection:
      if (!additive) {
        // emit deselection except to those plottables who will be selected
        // afterwards:
        foreach (BaseChartLayer* layer, mLayers) {
          foreach (BaseChartLayerable* layerable, layer->children()) {
            if ((potentialSelections.isEmpty() ||
                 potentialSelections.constBegin()->first != layerable) &&
                mInteractions.testFlag(layerable->selectionCategory())) {
              bool selChanged = false;
              layerable->deselectEvent(&selChanged);
              selectionStateChanged |= selChanged;
            }
          }
        }
      }

      // go through selections in reverse (largest selection first) and emit
      // select events:
      SelectionCandidates::const_iterator it = potentialSelections.constEnd();
      while (it != potentialSelections.constBegin()) {
        --it;
        if (mInteractions.testFlag(it.value().first->selectionCategory())) {
          bool selChanged = false;
          it.value().first->selectEvent(event, additive,
                                        QVariant::fromValue(it.value().second),
                                        &selChanged);
          selectionStateChanged |= selChanged;
        }
      }
    }
  }

  if (selectionStateChanged) {
    emit selectionChangedByUser();
    replot(rpQueuedReplot);
  } else if (mSelectionRect)
    mSelectionRect->layer()->replot();
}

/*! \internal

  This slot is connected to the selection rect's \ref
  BaseChartSelectionRect::accepted signal when \ref setSelectionRectMode is set
  to \ref srmZoom.

  It determines which axis rect was the origin of the selection rect judging by
  the starting point of the selection, and then zooms the axes defined via \ref
  BaseChartAxisRect::setRangeZoomAxes to the provided \a rect (see \ref
  BaseChartAxisRect::zoom).

  \see processRectSelection
*/
void BaseChartCustomPlot::processRectZoom(QRect rect, QMouseEvent* event) {
  Q_UNUSED(event)
  if (BaseChartAxisRect* axisRect = axisRectAt(rect.topLeft())) {
    QList<BaseChartAxis*> affectedAxes =
        QList<BaseChartAxis*>() << axisRect->rangeZoomAxes(Qt::Horizontal)
                                << axisRect->rangeZoomAxes(Qt::Vertical);
    affectedAxes.removeAll(static_cast<BaseChartAxis*>(nullptr));
    axisRect->zoom(QRectF(rect), affectedAxes);
  }
  replot(rpQueuedReplot);  // always replot to make selection rect disappear
}

/*! \internal

  This method is called when a simple left mouse click was detected on the
  BaseChartCustomPlot surface.

  It first determines the layerable that was hit by the click, and then calls
  its \ref BaseChartLayerable::selectEvent. All other layerables receive a
  BaseChartLayerable::deselectEvent (unless the multi-select modifier was
  pressed, see \ref setMultiSelectModifier).

  In this method the hit layerable is determined a second time using \ref
  layerableAt (after the one in \ref mousePressEvent), because we want \a
  onlySelectable set to true this time. This implies that the mouse event
  grabber (mMouseEventLayerable) may be a different one from the clicked
  layerable determined here. For example, if a non-selectable layerable is in
  front of a selectable layerable at the click position, the front layerable
  will receive mouse events but the selectable one in the back will receive the
  \ref BaseChartLayerable::selectEvent.

  \see processRectSelection, BaseChartLayerable::selectTest
*/
void BaseChartCustomPlot::processPointSelection(QMouseEvent* event) {
  QVariant details;
  BaseChartLayerable* clickedLayerable =
      layerableAt(event->pos(), true, &details);
  bool selectionStateChanged = false;
  bool additive = mInteractions.testFlag(iMultiSelect) &&
                  event->modifiers().testFlag(mMultiSelectModifier);
  // deselect all other layerables if not additive selection:
  if (!additive) {
    foreach (BaseChartLayer* layer, mLayers) {
      foreach (BaseChartLayerable* layerable, layer->children()) {
        if (layerable != clickedLayerable &&
            mInteractions.testFlag(layerable->selectionCategory())) {
          bool selChanged = false;
          layerable->deselectEvent(&selChanged);
          selectionStateChanged |= selChanged;
        }
      }
    }
  }
  if (clickedLayerable &&
      mInteractions.testFlag(clickedLayerable->selectionCategory())) {
    // a layerable was actually clicked, call its selectEvent:
    bool selChanged = false;
    clickedLayerable->selectEvent(event, additive, details, &selChanged);
    selectionStateChanged |= selChanged;
  }
  if (selectionStateChanged) {
    emit selectionChangedByUser();
    replot(rpQueuedReplot);
  }
}

/*! \internal

  Registers the specified plottable with this BaseChartCustomPlot and, if \ref
  setAutoAddPlottableToLegend is enabled, adds it to the legend
  (BaseChartCustomPlot::legend). BaseChartCustomPlot takes ownership of the
  plottable.

  Returns true on success, i.e. when \a plottable isn't already in this plot and
  the parent plot of \a plottable is this BaseChartCustomPlot.

  This method is called automatically in the BaseChartAbstractPlottable base
  class constructor.
*/
bool BaseChartCustomPlot::registerPlottable(
    BaseChartAbstractPlottable* plottable) {
  if (mPlottables.contains(plottable)) {
    qDebug() << Q_FUNC_INFO
             << "plottable already added to this BaseChartCustomPlot:"
             << reinterpret_cast<quintptr>(plottable);
    return false;
  }
  if (plottable->parentPlot() != this) {
    qDebug() << Q_FUNC_INFO
             << "plottable not created with this BaseChartCustomPlot as parent:"
             << reinterpret_cast<quintptr>(plottable);
    return false;
  }

  mPlottables.append(plottable);
  // possibly add plottable to legend:
  if (mAutoAddPlottableToLegend) plottable->addToLegend();
  if (!plottable
           ->layer())  // usually the layer is already set in the constructor of
                       // the plottable (via BaseChartLayerable constructor)
    plottable->setLayer(currentLayer());
  return true;
}

/*! \internal

  In order to maintain the simplified graph interface of BaseChartCustomPlot,
  this method is called by the BaseChartGraph constructor to register itself
  with this BaseChartCustomPlot's internal graph list. Returns true on success,
  i.e. if \a graph is valid and wasn't already registered with this
  BaseChartCustomPlot.

  This graph specific registration happens in addition to the call to \ref
  registerPlottable by the BaseChartAbstractPlottable base class.
*/
bool BaseChartCustomPlot::registerGraph(BaseChartGraph* graph) {
  if (!graph) {
    qDebug() << Q_FUNC_INFO << "passed graph is zero";
    return false;
  }
  if (mGraphs.contains(graph)) {
    qDebug() << Q_FUNC_INFO
             << "graph already registered with this BaseChartCustomPlot";
    return false;
  }

  mGraphs.append(graph);
  return true;
}

/*! \internal

  Registers the specified item with this BaseChartCustomPlot.
  BaseChartCustomPlot takes ownership of the item.

  Returns true on success, i.e. when \a item wasn't already in the plot and the
  parent plot of \a item is this BaseChartCustomPlot.

  This method is called automatically in the BaseChartAbstractItem base class
  constructor.
*/
bool BaseChartCustomPlot::registerItem(BaseChartAbstractItem* item) {
  if (mItems.contains(item)) {
    qDebug() << Q_FUNC_INFO << "item already added to this BaseChartCustomPlot:"
             << reinterpret_cast<quintptr>(item);
    return false;
  }
  if (item->parentPlot() != this) {
    qDebug() << Q_FUNC_INFO
             << "item not created with this BaseChartCustomPlot as parent:"
             << reinterpret_cast<quintptr>(item);
    return false;
  }

  mItems.append(item);
  if (!item->layer())  // usually the layer is already set in the constructor of
                       // the item (via BaseChartLayerable constructor)
    item->setLayer(currentLayer());
  return true;
}

/*! \internal

  Assigns all layers their index (BaseChartLayer::mIndex) in the mLayers list.
  This method is thus called after every operation that changes the layer
  indices, like layer removal, layer creation, layer moving.
*/
void BaseChartCustomPlot::updateLayerIndices() const {
  for (int i = 0; i < mLayers.size(); ++i) mLayers.at(i)->mIndex = i;
}

/*! \internal

  Returns the top-most layerable at pixel position \a pos. If \a onlySelectable
  is set to true, only those layerables that are selectable will be considered.
  (Layerable subclasses communicate their selectability via the
  BaseChartLayerable::selectTest method, by returning -1.)

  \a selectionDetails is an output parameter that contains selection specifics
  of the affected layerable. This is useful if the respective layerable shall be
  given a subsequent BaseChartLayerable::selectEvent (like in \ref
  mouseReleaseEvent). \a selectionDetails usually contains information about
  which part of the layerable was hit, in multi-part layerables (e.g.
  BaseChartAxis::SelectablePart). If the layerable is a plottable, \a
  selectionDetails contains a \ref BaseChartDataSelection instance with the
  single data point which is closest to \a pos.

  \see layerableListAt, layoutElementAt, axisRectAt
*/
BaseChartLayerable* BaseChartCustomPlot::layerableAt(
    const QPointF& pos, bool onlySelectable, QVariant* selectionDetails) const {
  QList<QVariant> details;
  QList<BaseChartLayerable*> candidates = layerableListAt(
      pos, onlySelectable, selectionDetails ? &details : nullptr);
  if (selectionDetails && !details.isEmpty())
    *selectionDetails = details.first();
  if (!candidates.isEmpty())
    return candidates.first();
  else
    return nullptr;
}

/*! \internal

  Returns the layerables at pixel position \a pos. If \a onlySelectable is set
  to true, only those layerables that are selectable will be considered.
  (Layerable subclasses communicate their selectability via the
  BaseChartLayerable::selectTest method, by returning -1.)

  The returned list is sorted by the layerable/drawing order such that the
  layerable that appears on top in the plot is at index 0 of the returned list.
  If you only need to know the top layerable, rather use \ref layerableAt.

  \a selectionDetails is an output parameter that contains selection specifics
  of the affected layerable. This is useful if the respective layerable shall be
  given a subsequent BaseChartLayerable::selectEvent (like in \ref
  mouseReleaseEvent). \a selectionDetails usually contains information about
  which part of the layerable was hit, in multi-part layerables (e.g.
  BaseChartAxis::SelectablePart). If the layerable is a plottable, \a
  selectionDetails contains a \ref BaseChartDataSelection instance with the
  single data point which is closest to \a pos.

  \see layerableAt, layoutElementAt, axisRectAt
*/
QList<BaseChartLayerable*> BaseChartCustomPlot::layerableListAt(
    const QPointF& pos, bool onlySelectable,
    QList<QVariant>* selectionDetails) const {
  QList<BaseChartLayerable*> result;
  for (int layerIndex = mLayers.size() - 1; layerIndex >= 0; --layerIndex) {
    const QList<BaseChartLayerable*> layerables =
        mLayers.at(layerIndex)->children();
    for (int i = layerables.size() - 1; i >= 0; --i) {
      if (!layerables.at(i)->realVisibility()) continue;
      QVariant details;
      double dist = layerables.at(i)->selectTest(
          pos, onlySelectable, selectionDetails ? &details : nullptr);
      if (dist >= 0 && dist < selectionTolerance()) {
        result.append(layerables.at(i));
        if (selectionDetails) selectionDetails->append(details);
      }
    }
  }
  return result;
}

/*!
  Saves the plot to a rastered image file \a fileName in the image format \a
  format. The plot is sized to \a width and \a height in pixels and scaled with
  \a scale. (width 100 and scale 2.0 lead to a full resolution file with width
  200.) If the \a format supports compression, \a quality may be between 0 and
  100 to control it.

  Returns true on success. If this function fails, most likely the given \a
  format isn't supported by the system, see Qt docs about
  QImageWriter::supportedImageFormats().

  The \a resolution will be written to the image file header (if the file format
  supports this) and has no direct consequence for the quality or the pixel
  size. However, if opening the image with a tool which respects the metadata,
  it will be able to scale the image to match either a given size in real units
  of length (inch, centimeters, etc.), or the target display DPI. You can
  specify in which units \a resolution is given, by setting \a resolutionUnit.
  The \a resolution is converted to the format's expected resolution unit
  internally.

  \see saveBmp, saveJpg, savePng, savePdf
*/
bool BaseChartCustomPlot::saveRastered(const QString& fileName, int width,
                                       int height, double scale,
                                       const char* format, int quality,
                                       int resolution,
                                       ResolutionUnit resolutionUnit) {
  QImage buffer = toPixmap(width, height, scale).toImage();

  int dotsPerMeter = 0;
  switch (resolutionUnit) {
    case ruDotsPerMeter:
      dotsPerMeter = resolution;
      break;
    case ruDotsPerCentimeter:
      dotsPerMeter = resolution * 100;
      break;
    case ruDotsPerInch:
      dotsPerMeter = int(resolution / 0.0254);
      break;
  }
  buffer.setDotsPerMeterX(
      dotsPerMeter);  // this is saved together with some image formats, e.g.
                      // PNG, and is relevant when opening image in other tools
  buffer.setDotsPerMeterY(
      dotsPerMeter);  // this is saved together with some image formats, e.g.
                      // PNG, and is relevant when opening image in other tools
  if (!buffer.isNull())
    return buffer.save(fileName, format, quality);
  else
    return false;
}

/*!
  Renders the plot to a pixmap and returns it.

  The plot is sized to \a width and \a height in pixels and scaled with \a
  scale. (width 100 and scale 2.0 lead to a full resolution pixmap with width
  200.)

  \see toPainter, saveRastered, saveBmp, savePng, saveJpg, savePdf
*/
QPixmap BaseChartCustomPlot::toPixmap(int width, int height, double scale) {
  // this method is somewhat similar to toPainter. Change something here, and a
  // change in toPainter might be necessary, too.
  int newWidth, newHeight;
  if (width == 0 || height == 0) {
    newWidth = this->width();
    newHeight = this->height();
  } else {
    newWidth = width;
    newHeight = height;
  }
  int scaledWidth = qRound(scale * newWidth);
  int scaledHeight = qRound(scale * newHeight);

  QPixmap result(scaledWidth, scaledHeight);
  result.fill(
      mBackgroundBrush.style() == Qt::SolidPattern
          ? mBackgroundBrush.color()
          : Qt::transparent);  // if using non-solid pattern, make transparent
                               // now and draw brush pattern later
  BaseChartPainter painter;
  painter.begin(&result);
  if (painter.isActive()) {
    QRect oldViewport = viewport();
    setViewport(QRect(0, 0, newWidth, newHeight));
    painter.setMode(BaseChartPainter::pmNoCaching);
    if (!qFuzzyCompare(scale, 1.0)) {
      if (scale >
          1.0)  // for scale < 1 we always want cosmetic pens where possible,
                // because else lines might disappear for very small scales
        painter.setMode(BaseChartPainter::pmNonCosmetic);
      painter.scale(scale, scale);
    }
    if (mBackgroundBrush.style() != Qt::SolidPattern &&
        mBackgroundBrush.style() !=
            Qt::NoBrush)  // solid fills were done a few lines above with
                          // QPixmap::fill
      painter.fillRect(mViewport, mBackgroundBrush);
    draw(&painter);
    setViewport(oldViewport);
    painter.end();
  } else  // might happen if pixmap has width or height zero
  {
    qDebug() << Q_FUNC_INFO << "Couldn't activate painter on pixmap";
    return QPixmap();
  }
  return result;
}

/*!
  Renders the plot using the passed \a painter.

  The plot is sized to \a width and \a height in pixels. If the \a painter's
  scale is not 1.0, the resulting plot will appear scaled accordingly.

  \note If you are restricted to using a QPainter (instead of BaseChartPainter),
  create a temporary QPicture and open a BaseChartPainter on it. Then call \ref
  toPainter with this BaseChartPainter. After ending the paint operation on the
  picture, draw it with the QPainter. This will reproduce the painter actions
  the BaseChartPainter took, with a QPainter.

  \see toPixmap
*/
void BaseChartCustomPlot::toPainter(BaseChartPainter* painter, int width,
                                    int height) {
  // this method is somewhat similar to toPixmap. Change something here, and a
  // change in toPixmap might be necessary, too.
  int newWidth, newHeight;
  if (width == 0 || height == 0) {
    newWidth = this->width();
    newHeight = this->height();
  } else {
    newWidth = width;
    newHeight = height;
  }

  if (painter->isActive()) {
    QRect oldViewport = viewport();
    setViewport(QRect(0, 0, newWidth, newHeight));
    painter->setMode(BaseChartPainter::pmNoCaching);
    if (mBackgroundBrush.style() !=
        Qt::NoBrush)  // unlike in toPixmap, we can't do QPixmap::fill for
                      // Qt::SolidPattern brush style, so we also draw solid
                      // fills with fillRect here
      painter->fillRect(mViewport, mBackgroundBrush);
    draw(painter);
    setViewport(oldViewport);
  } else
    qDebug() << Q_FUNC_INFO << "Passed painter is not active";
}